#!/usr/bin/python3
# Copyright (C) 2025  FreeIPA Contributors see COPYING for license

"""
pki-server - PKI server management command (Dogtag-compatible)

Manages ipacta server instances, compatible with Dogtag's pki-server command.
"""

import sys
import argparse
import subprocess
from pathlib import Path

from ipacta.core.paths import paths


class PKIServerCLI:
    """PKI server management CLI"""

    def __init__(self):
        self.verbose = False
        self.instance_name = 'pki-tomcat'  # Default instance name

    def cmd_status(self, args):
        """Show server status"""
        parser = argparse.ArgumentParser(prog='pki-server status')
        parser.add_argument('instance', nargs='?',
                            default=self.instance_name,
                            help='Instance name')

        opts = parser.parse_args(args)

        # Check systemd service status — FreeIPA uses the
        # pki-tomcatd@pki-tomcat alias, try both
        status = 'inactive'
        for svc in ['pki-tomcatd@pki-tomcat', 'ipacta']:
            result = subprocess.run(
                ['systemctl', 'is-active', svc],
                capture_output=True, check=False,
                text=True
            )
            if result.stdout.strip() == 'active':
                status = 'active'
                break
        if status == 'inactive':
            status = result.stdout.strip()
        print(f"Instance: {opts.instance}")
        print(f"Status: {status}")

        if status == 'active':
            # Get PID from the service that matched
            pid_result = subprocess.run(
                ['systemctl', 'show', '-p', 'MainPID', svc],
                capture_output=True, check=False,
                text=True
            )
            if pid_result.returncode == 0:
                pid = pid_result.stdout.strip().split('=')[1]
                print(f"PID: {pid}")

        return 0 if status == 'active' else 1

    def cmd_start(self, args):
        """Start server"""
        parser = argparse.ArgumentParser(prog='pki-server start')
        parser.add_argument('instance', nargs='?', default=self.instance_name)

        opts = parser.parse_args(args)

        print(f"Starting instance {opts.instance}...")
        result = subprocess.run(
            ['systemctl', 'start', 'ipacta'], check=False)

        if result.returncode == 0:
            print("Server started")
        else:
            print("Failed to start server", file=sys.stderr)

        return result.returncode

    def cmd_stop(self, args):
        """Stop server"""
        parser = argparse.ArgumentParser(prog='pki-server stop')
        parser.add_argument('instance', nargs='?', default=self.instance_name)

        opts = parser.parse_args(args)

        print(f"Stopping instance {opts.instance}...")
        result = subprocess.run(
            ['systemctl', 'stop', 'ipacta'], check=False)

        if result.returncode == 0:
            print("Server stopped")
        else:
            print("Failed to stop server", file=sys.stderr)

        return result.returncode

    def cmd_restart(self, args):
        """Restart server"""
        parser = argparse.ArgumentParser(prog='pki-server restart')
        parser.add_argument('instance', nargs='?', default=self.instance_name)

        opts = parser.parse_args(args)

        print(f"Restarting instance {opts.instance}...")
        result = subprocess.run(
            ['systemctl', 'restart', 'ipacta'], check=False)

        if result.returncode == 0:
            print("Server restarted")
        else:
            print("Failed to restart server", file=sys.stderr)

        return result.returncode

    def cmd_instance_show(self, args):
        """Show instance details"""
        parser = argparse.ArgumentParser(prog='pki-server instance-show')
        parser.add_argument('instance', nargs='?', default=self.instance_name)

        opts = parser.parse_args(args)

        print(f"Instance: {opts.instance}")
        print("Type: ipacta")

        # Read configuration
        config_file = Path(paths.IPACTA_CONF)
        if config_file.exists():
            print(f"Config: {config_file}")
            # Parse config and show key values
            with open(config_file) as f:
                for line in f:
                    if line.strip() and not line.startswith('#'):
                        print(f"  {line.strip()}")
        else:
            print("Warning: Configuration file not found")

        return 0

    def cmd_subsystem_show(self, args):
        """Show subsystem details"""
        parser = argparse.ArgumentParser(prog='pki-server subsystem-show')
        parser.add_argument('-i', '--instance', default=self.instance_name)
        parser.add_argument('subsystem_id', nargs='?',
                            help='Subsystem ID (ca, kra)')

        opts = parser.parse_args(args)

        if opts.subsystem_id is None:
            print('ERROR: Missing subsystem ID', file=sys.stderr)
            return 1

        subsystem_id = opts.subsystem_id.lower()

        installed = self._get_installed_subsystems()
        if subsystem_id not in installed:
            print(
                'ERROR: No %s subsystem in instance %s.'
                % (subsystem_id, opts.instance),
                file=sys.stderr,
            )
            return 1

        print(f"  Subsystem ID: {subsystem_id}")
        print(f"  Instance ID: {opts.instance}")
        print(f"  Enabled: {installed[subsystem_id]['enabled']}")

        if subsystem_id == 'ca':
            print("  Type: Certificate Authority")
        elif subsystem_id == 'kra':
            print("  Type: Data Recovery Manager")

        return 0

    def _get_installed_subsystems(self):
        """Return dict of installed subsystems and their state.

        Detection mirrors Dogtag: check the instance registry file at
        /etc/sysconfig/pki/tomcat/pki-tomcat and the per-subsystem
        CS.cfg at /var/lib/pki/pki-tomcat/conf/<sub>/CS.cfg.
        """
        result = {}

        # CA is always present when ipacta is installed
        config_file = Path(paths.IPACTA_CONF)
        if config_file.exists():
            result['ca'] = {'enabled': True}

        # Check instance registry (written by pkispawn)
        registry_file = Path(
            f'/etc/sysconfig/pki/tomcat/{self.instance_name}')
        if registry_file.exists():
            content = registry_file.read_text()
            if '[CA]' in content:
                result['ca'] = {'enabled': True}
            if '[KRA]' in content:
                result['kra'] = {'enabled': True}

        # Check for KRA CS.cfg (written by pkispawn -s KRA)
        kra_cfg = Path(
            f'/var/lib/pki/{self.instance_name}/conf/kra/CS.cfg')
        if kra_cfg.exists():
            result.setdefault('kra', {'enabled': True})

        return result

    def cmd_ca(self, args):
        """CA subsystem commands"""
        if not args:
            print("CA subsystem commands:")
            print("  config-find    Show all CA configuration")
            print("  config-show    Show CA configuration parameter")
            print("  config-set     Set CA configuration parameter")
            print("  config-unset   Unset CA configuration parameter")
            print("  cert-list      List CA certificates")
            return 0

        subcmd = args[0]
        if subcmd == 'config-find':
            return self.cmd_ca_config_find(args[1:])
        elif subcmd == 'config-show':
            return self.cmd_ca_config_show(args[1:])
        elif subcmd == 'config-set':
            return self.cmd_ca_config_set(args[1:])
        elif subcmd == 'config-unset':
            return self.cmd_ca_config_unset(args[1:])
        elif subcmd == 'cert-list':
            return self.cmd_ca_cert_list(args[1:])
        else:
            print(f"Error: Unknown CA command '{subcmd}'", file=sys.stderr)
            return 1

    def cmd_ca_config_find(self, args):
        """Show all CA configuration (FreeIPA filters output)"""
        # Read ipacta config
        config_file = Path(paths.IPACTA_CONF)
        if not config_file.exists():
            print("Error: Configuration file not found", file=sys.stderr)
            return 1

        import configparser
        config = configparser.ConfigParser()
        config.read(config_file)

        # Print all config in format: section.key=value
        for section in config.sections():
            for key in config.options(section):
                value = config.get(section, key)
                print(f"{section}.{key}={value}")

        return 0

    def cmd_ca_config_show(self, args):
        """Show CA configuration parameter"""
        parser = argparse.ArgumentParser(prog='pki-server ca-config-show')
        parser.add_argument(
            'parameter',
            help='Configuration parameter to show')

        opts = parser.parse_args(args)

        # Read ipacta config
        config_file = Path(paths.IPACTA_CONF)
        if not config_file.exists():
            print("Error: Configuration file not found",
                  file=sys.stderr)
            return 1

        # Parse config file for the parameter
        import configparser
        config = configparser.ConfigParser()
        config.read(config_file)

        # Parameter can be in format: section.key or just key
        if '.' in opts.parameter:
            parts = opts.parameter.rsplit('.', 1)
            section = parts[0] if len(parts) > 1 else 'ca'
            key = parts[-1]
        else:
            section = 'ca'
            key = opts.parameter

        try:
            value = config.get(section, key)
            print(value)
            return 0
        except (configparser.NoSectionError, configparser.NoOptionError):
            # Not found, return empty
            print("")
            return 0

    def cmd_ca_config_set(self, args):
        """Set CA configuration parameter"""
        parser = argparse.ArgumentParser(prog='pki-server ca-config-set')
        parser.add_argument('parameter', help='Configuration parameter')
        parser.add_argument('value', help='Value to set')

        opts = parser.parse_args(args)

        # Write to ipacta config
        config_file = Path(paths.IPACTA_CONF)
        if not config_file.exists():
            print("Error: Configuration file not found", file=sys.stderr)
            return 1

        import configparser
        config = configparser.ConfigParser()
        config.read(config_file)

        # Parameter format: section.key
        if '.' in opts.parameter:
            parts = opts.parameter.rsplit('.', 1)
            section = parts[0] if len(parts) > 1 else 'ca'
            key = parts[-1]
        else:
            section = 'ca'
            key = opts.parameter

        # Ensure section exists
        if not config.has_section(section):
            config.add_section(section)

        # Set value
        config.set(section, key, opts.value)

        # Write back
        with open(config_file, 'w') as f:
            config.write(f)

        print(f"Set {opts.parameter} = {opts.value}")
        return 0

    def cmd_ca_config_unset(self, args):
        """Unset CA configuration parameter"""
        parser = argparse.ArgumentParser(prog='pki-server ca-config-unset')
        parser.add_argument(
            'parameter',
            help='Configuration parameter to remove')

        opts = parser.parse_args(args)

        # Write to ipacta config
        config_file = Path(paths.IPACTA_CONF)
        if not config_file.exists():
            print("Error: Configuration file not found", file=sys.stderr)
            return 1

        import configparser
        config = configparser.ConfigParser()
        config.read(config_file)

        # Parameter format: section.key
        if '.' in opts.parameter:
            parts = opts.parameter.rsplit('.', 1)
            section = parts[0] if len(parts) > 1 else 'ca'
            key = parts[-1]
        else:
            section = 'ca'
            key = opts.parameter

        # Remove option
        removed = False
        if config.has_section(section):
            removed = config.remove_option(section, key)

        if removed:
            # Write back
            with open(config_file, 'w') as f:
                config.write(f)
            print(f"Unset {opts.parameter}")
        else:
            print(f"Parameter {opts.parameter} not found")

        return 0

    def cmd_ca_cert_list(self, args):
        """List CA certificates"""
        print("CA Certificates:")

        # List certs in NSSDB
        nssdb_dir = Path(paths.PKI_TOMCAT_ALIAS_DIR)
        if nssdb_dir.exists():
            result = subprocess.run(
                ['certutil', '-L', '-d', str(nssdb_dir)],
                capture_output=True, check=False,
                text=True
            )
            print(result.stdout)
        else:
            print("Warning: NSSDB not found")

        return 0

    def cmd_instance(self, args):
        """Instance management commands"""
        if not args:
            print("Instance commands:")
            print("  show           Show instance details")
            return 0

        subcmd = args[0]
        if subcmd == 'show':
            return self.cmd_instance_show(args[1:])
        else:
            print(
                f"Error: Unknown instance command '{subcmd}'",
                file=sys.stderr)
            return 1

    def cmd_subsystem(self, args):
        """Subsystem management commands"""
        if not args:
            print("Subsystem commands:")
            print("  show           Show subsystem details")
            return 0

        subcmd = args[0]
        if subcmd == 'show':
            return self.cmd_subsystem_show(args[1:])
        else:
            print(
                f"Error: Unknown subsystem command '{subcmd}'",
                file=sys.stderr)
            return 1

    def cmd_acme_create(self, args):
        """Create ACME service"""
        print("Creating ACME service...")

        # Dogtag compat: FreeIPA checks for this directory to detect
        # ACME deployment
        dogtag_acme = Path('/etc/pki/pki-tomcat/acme')
        dogtag_acme.mkdir(parents=True, exist_ok=True)

        print("ACME service created")
        return 0

    def cmd_acme_deploy(self, args):
        """Deploy ACME service"""
        print("Deploying ACME service...")
        print("ACME service deployed")
        return 0

    def cmd_acme_remove(self, args):
        """Remove ACME service"""
        print("Removing ACME service...")
        print("ACME service removed")
        return 0

    def cmd_cert_fix(self, args):
        """Fix expired certificates (compatibility with Dogtag)"""
        parser = argparse.ArgumentParser(prog='pki-server cert-fix')
        parser.add_argument('--extra-cert', action='append',
                            dest='extra_certs',
                            help='Additional certificate serial'
                                 ' to renew')
        parser.add_argument('--ldap-url',
                            help='LDAP server URL')
        parser.add_argument('--ldap-bind-dn',
                            help='LDAP bind DN')
        parser.add_argument('--ldap-bind-password-file',
                            help='LDAP password file')
        parser.add_argument('--agent-uid', default='ipara',
                            help='Agent user ID')

        opts = parser.parse_args(args)

        print("ipacta cert-fix: Certificate renewal")
        print()
        print("ipacta uses certmonger for automatic certificate renewal.")
        print("Certificates should be renewed automatically via certmonger.")
        print()

        if opts.extra_certs:
            print("Note: Would renew "
                  f"{len(opts.extra_certs)} extra certificates")
            print("In ipacta, configure certmonger tracking"
                  " for these certificates")

        print()
        print("For manual certificate renewal:")
        print("  1. Use 'ipa-getcert list' to see tracked certificates")
        print("  2. Use 'ipa-getcert resubmit' to force renewal")
        print("  3. Or use 'pki ca-cert-request-submit' for new requests")

        return 0

    def cmd_kra(self, args):
        """KRA subsystem commands"""
        if not args:
            print("KRA subsystem commands:")
            print("  clone-prepare  Export KRA keys for replica")
            return 0

        subcmd = args[0]
        if subcmd == 'clone-prepare':
            return self.cmd_kra_clone_prepare(args[1:])
        else:
            print(
                f"Error: Unknown KRA command '{subcmd}'",
                file=sys.stderr)
            return 1

    def cmd_kra_clone_prepare(self, args):
        """Export KRA storage and transport certs for clone setup.

        Compatible with Dogtag's pki-server kra-clone-prepare.
        FreeIPA's krainstance.py calls this on the master before
        transferring keys to the replica via Custodia.
        """
        parser = argparse.ArgumentParser(
            prog='pki-server kra-clone-prepare')
        parser.add_argument(
            '--pkcs12-file', required=True,
            help='Output PKCS#12 file path')
        parser.add_argument(
            '--pkcs12-password-file', required=True,
            help='File containing PKCS#12 password')
        parser.add_argument(
            '-i', '--instance', default=self.instance_name,
            help='Instance name')

        opts = parser.parse_args(args)

        pwd_path = Path(opts.pkcs12_password_file)
        if not pwd_path.exists():
            print(
                f"Error: Password file not found: {pwd_path}",
                file=sys.stderr)
            return 1

        backup_password = pwd_path.read_text().strip()

        nssdb_dir = paths.PKI_TOMCAT_ALIAS_DIR
        if not Path(nssdb_dir).is_dir():
            print("Error: NSSDB not found", file=sys.stderr)
            return 1

        import os
        import tempfile

        nssdb_pwdfile = os.path.join(nssdb_dir, 'pwdfile.txt')
        if not os.path.isfile(nssdb_pwdfile):
            print("Error: NSSDB password file not found",
                  file=sys.stderr)
            return 1

        p12_out = Path(opts.pkcs12_file)
        p12_out.parent.mkdir(parents=True, exist_ok=True)

        kra_nicknames = [
            "storageCert cert-pki-kra",
            "transportCert cert-pki-kra",
        ]

        with tempfile.NamedTemporaryFile(
            mode='w', suffix='.txt', delete=False
        ) as f:
            f.write(backup_password)
            pwd_file = f.name

        try:
            with tempfile.TemporaryDirectory() as tmpdir:
                tmp_nssdb = os.path.join(tmpdir, "nssdb")
                os.makedirs(tmp_nssdb)

                subprocess.run(
                    ["certutil", "-N", "-d", f"sql:{tmp_nssdb}",
                     "-f", nssdb_pwdfile],
                    capture_output=True, check=True,
                    stdin=subprocess.DEVNULL,
                )

                exported = 0
                for nickname in kra_nicknames:
                    slug = nickname.replace(" ", "_")
                    tmp_p12 = os.path.join(
                        tmpdir, f"{slug}.p12")
                    result = subprocess.run(
                        ['pk12util', '-o', tmp_p12,
                         '-n', nickname,
                         '-d', f'sql:{nssdb_dir}',
                         '-k', nssdb_pwdfile,
                         '-w', pwd_file],
                        capture_output=True, check=False,
                        stdin=subprocess.DEVNULL,
                    )
                    if result.returncode != 0:
                        print(
                            f"Warning: {nickname} not found "
                            "in NSSDB, skipping",
                            file=sys.stderr)
                        continue
                    subprocess.run(
                        ['pk12util', '-i', tmp_p12,
                         '-d', f'sql:{tmp_nssdb}',
                         '-k', nssdb_pwdfile,
                         '-w', pwd_file],
                        capture_output=True, check=False,
                        stdin=subprocess.DEVNULL,
                    )
                    exported += 1

                if exported == 0:
                    print(
                        "Error: No KRA certs found in NSSDB",
                        file=sys.stderr)
                    return 1

                subprocess.run(
                    ['pk12util', '-o', str(p12_out),
                     '-d', f'sql:{tmp_nssdb}',
                     '-k', nssdb_pwdfile,
                     '-w', pwd_file],
                    capture_output=True, check=True,
                    stdin=subprocess.DEVNULL,
                )
        finally:
            os.unlink(pwd_file)

        p12_out.chmod(0o600)
        print(f"Exported {exported} KRA certs to {p12_out}")
        return 0

    def cmd_migrate(self, args):
        """Migrate from Dogtag"""
        parser = argparse.ArgumentParser(prog='pki-server migrate')
        parser.add_argument('--dogtag-instance',
                            default='pki-tomcat',
                            help='Dogtag instance name')

        opts = parser.parse_args(args)

        print(f"Migrating from Dogtag instance: {opts.dogtag_instance}")
        print("This will:")
        print("  1. Export CA signing certificate and key from Dogtag")
        print("  2. Export LDAP data")
        print("  3. Import into ipacta")
        print()
        print("Migration tool not yet implemented.")
        print("For manual migration, see: doc/migration-from-dogtag.md")

        return 1

    def run(self, argv):
        """Main entry point"""
        if len(argv) == 1 or '--help' in argv or '-h' in argv:
            self.print_help()
            return 0

        # Parse command
        args = argv[1:]

        # Check for verbose flag
        if '-v' in args or '--verbose' in args:
            self.verbose = True
            args = [a for a in args if a not in ['-v', '--verbose']]

        if not args:
            self.print_help()
            return 0

        command = args[0]
        cmd_args = args[1:]

        # Command dispatch
        cmd_map = {
            'status': self.cmd_status,
            'start': self.cmd_start,
            'stop': self.cmd_stop,
            'restart': self.cmd_restart,
            'instance': self.cmd_instance,
            'subsystem': self.cmd_subsystem,
            'ca': self.cmd_ca,
            'kra': self.cmd_kra,
            'acme-create': self.cmd_acme_create,
            'acme-deploy': self.cmd_acme_deploy,
            'acme-remove': self.cmd_acme_remove,
            'cert-fix': self.cmd_cert_fix,
            'migrate': self.cmd_migrate,
            # Legacy aliases for backwards compat
            'instance-show': self.cmd_instance_show,
            'subsystem-show': self.cmd_subsystem_show,
            'kra-clone-prepare': self.cmd_kra_clone_prepare,
            'ca-config-find': self.cmd_ca_config_find,
            'ca-config-show': self.cmd_ca_config_show,
            'ca-config-set': self.cmd_ca_config_set,
            'ca-config-unset': self.cmd_ca_config_unset,
            'ca-cert-list': self.cmd_ca_cert_list,
        }

        if command in cmd_map:
            return cmd_map[command](cmd_args) or 0
        else:
            print(f"Error: Unknown command '{command}'", file=sys.stderr)
            return 1

    def print_help(self):
        """Print help"""
        print("""Usage: pki-server [OPTIONS] <command> [command-options]

Options:
  -i, --instance <instance>     Instance ID (default: pki-tomcat)
  -v, --verbose                 Verbose mode
      --help                    Show help
      --version                 Show version

Commands:
  status                        Display PKI service status
  start                         Start PKI service
  stop                          Stop PKI service
  restart                       Restart PKI service
  instance                      Instance management commands
    show                        Show instance details
  subsystem                     Subsystem management commands
    show                        Show subsystem details
  ca                            CA management commands
    config-show <param>         Show CA configuration parameter
    config-set <param> <value>  Set CA configuration parameter
    cert-list                   List CA certificates
  kra                           KRA management commands
    clone-prepare               Export KRA keys for replica
  acme-create                   Create ACME service
  acme-deploy                   Deploy ACME service
  acme-remove                   Remove ACME service
  cert-fix                      Fix expired certificates
  migrate                       Migrate from Dogtag

Compatible with Dogtag pki-server commands (FreeIPA integration).
""")


def main():
    """Main entry point"""
    cli = PKIServerCLI()
    try:
        sys.exit(cli.run(sys.argv))
    except KeyboardInterrupt:
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        if cli.verbose:
            import traceback
            traceback.print_exc()
        sys.exit(1)


if __name__ == '__main__':
    main()
