from colorama import Fore, Style
from tabulate import tabulate

from tcs_garr.commands.base import BaseCommand
from tcs_garr.utils import UserRole, format_date_and_check_expiry


class AcmeAccountsCommand(BaseCommand):
    """
    Command to list available acme accounts and display their details.


    Args:
        args (argparse.Namespace): The command-line arguments passed to the command.
    """

    REQUIRED_ROLE = UserRole.ENTERPRISE_ADMIN

    def __init__(self, args):
        super().__init__(args)
        self.command_name = "acme"
        self.help_text = "List ACME accounts configured in Harica"

    def configure_parser(self, parser):
        """
        Configure the argument parser for the acme command.

        This method is overridden from the BaseCommand class but is not used
        for the 'acme' command as it does not require any additional arguments.

        Args:
            parser: An argparse.ArgumentParser object used for parsing command-line arguments.
        """
        subparsers = parser.add_subparsers(dest="action")

        # Subcommand to list ACME accounts
        list_parser = subparsers.add_parser("list", help="List all ACME accounts")
        list_parser.add_argument("--show-disabled", action="store_true", help="Show also disabled ACME accounts")

        # Subcommand to get info on a specific ACME account
        info_parser = subparsers.add_parser("info", help="Get information on a specific ACME account including secrets")
        info_parser.add_argument("account_id", type=str, help="ID of the ACME account to retrieve information for")

        # Subcommand to create a new ACME account
        create_parser = subparsers.add_parser("create", help="Create a new ACME account")
        create_parser.add_argument("friendly_name", type=str, help="Friendly name for the new ACME account")
        create_parser.add_argument(
            "--transaction_type",
            type=str,
            default="SSL OV",
            choices=["SSL OV", "SSL DV"],
            help="Transaction type for the new ACME account",
        )

        # Subcommand to disable an ACME account
        disable_parser = subparsers.add_parser("disable", help="Disable an ACME account")
        disable_parser.add_argument("account_id", type=str, help="ID of the ACME account to disable")

        # Subcommand for domains
        domains_parser = subparsers.add_parser("domains", help="Perform actions on ACME account domains and rules")
        domains_parser.add_argument("account_id", type=str, help="ID of the ACME account to retrieve domains for")
        domains_parser.add_argument("--list", action="store_true", help="List available domains for the ACME account")
        domains_parser.add_argument("--active", action="store_true", help="List active rules for the ACME account")
        domains_parser.add_argument("--inactive", action="store_true", help="List inactive rules for the ACME account")

        # Subsubcommand for domain to add or remove rules
        domains_subparsers = domains_parser.add_subparsers(dest="domain_action")

        # Add
        add_parser = domains_subparsers.add_parser("add", help="Add a domain rule to an ACME account")
        add_parser.add_argument("domain", type=str, nargs="?", help="Domain to add to the ACME account (e.g., mydomain.tld)")
        add_parser.add_argument("--all", action="store_true", help="Add all domains to the ACME account")
        add_parser.add_argument(
            "--subdomain", type=str, help="Subdomain to add to the ACME account (e.g., subdomain.mydomain.tld)"
        )
        add_parser.add_argument(
            "--no-subdomains",
            action="store_false",
            help="Do NOT apply the domain to subdomains (default: applies to subdomains)",
        )

        add_parser.add_argument(
            "--not-allowed",
            action="store_false",
            help="Mark the domain as NOT allowed (default: allowed)",
        )

        # Remove
        remove_parser = domains_subparsers.add_parser("remove", help="Remove a domain rule from an ACME account")
        remove_parser.add_argument(
            "rule_id",
            type=str,
            help="Rule ID to remove from the ACME account. Use '--active' or '--inactive' to list rules to remove.",
        )

    def execute(self):
        """
        Executes the command to list ACME accounts from Harica.
        """
        action = self.args.action

        if action == "list":
            self._list_accounts(self.args.show_disabled)
        elif action == "info":
            self._account_info(self.args.account_id)
        elif action == "create":
            self._create_account(self.args.friendly_name, self.args.transaction_type)
        elif action == "disable":
            self.logger.info(f"{Fore.YELLOW}Disabling ACME account:{Style.RESET_ALL}\n")
            self._account_info(self.args.account_id)

            confirm = input(f"{Fore.RED}Are you sure you want to disable this ACME account? (Y/n): {Style.RESET_ALL}")
            if confirm == "Y":
                self._disable_account()
            else:
                self.logger.info("Operation cancelled.")
        elif action == "domains":
            domain_action = self.args.domain_action

            if not domain_action:
                if self.args.list:
                    self._list_available_domains()

                domains = []

                # Avoid calling the API twice if both --active and --inactive are
                # specified
                if self.args.active or self.args.inactive:
                    domains = self.harica_client.get_acme_domains(self.args.account_id)

                if self.args.active:
                    self._list_active_inactive_domains(domains, True)
                if self.args.inactive:
                    self._list_active_inactive_domains(domains, False)

            elif domain_action == "add":
                if not self.args.all and not self.args.domain:
                    self.logger.error("You must specify either a domain or --all when adding domains.")
                    return

                if self.args.all:
                    confirm = input(
                        f"{Fore.RED}Are you sure you want to add all domains to account ID '{self.args.account_id}'? (Y/n): {Style.RESET_ALL}"
                    )
                    if confirm == "Y":
                        self._add_all_domains()
                    else:
                        self.logger.info("Operation cancelled.")
                elif self.args.domain:
                    self._add_domain_rule()

            elif domain_action == "remove":
                if self.args.rule_id:
                    self._remove_domain_rule()

    def _list_available_domains(self):
        """List available domains for an ACME account."""
        self.logger.info(f"{Fore.BLUE}Listing available domains for account ID: {self.args.account_id}{Style.RESET_ALL}\n")
        domains = self.harica_client.get_acme_available_domains(self.args.account_id)
        table_data = []

        for domain in domains:
            domain_name = domain.get("domain")
            validity, expired = format_date_and_check_expiry(domain.get("validity"))
            expired_str = "Yes" if expired else "No"
            color = Fore.RED if expired else ""

            table_data.append(
                [
                    f"{color}{domain_name}{Style.RESET_ALL}",
                    f"{color}{validity}{Style.RESET_ALL}",
                    f"{color}{expired_str}{Style.RESET_ALL}",
                ]
            )

        self.logger.info(
            tabulate(
                table_data,
                headers=[
                    f"{Fore.BLUE}Domain",
                    "Validity",
                    f"Expired{Style.RESET_ALL}",
                ],
                tablefmt="grid",
            )
        )

    def _list_active_inactive_domains(self, domains: list[dict], active: bool):
        """List active or inactive domains for an ACME account.

        Parameters
        ----------
        domains : list[dict]
            List of active and inactive domains
        active : bool
            Whether to list active or inactive domains

        """
        self.logger.info(
            f"{Fore.BLUE}Listing {'active' if active else 'inactive'} domains for account ID: {self.args.account_id}{Style.RESET_ALL}\n"
        )
        table_data = []

        for domain in domains:
            enabled = domain.get("isEnabled")

            if active and not enabled:
                continue

            if not active and enabled:
                continue

            rule_id = domain.get("id")
            fqdn = domain.get("fqdn")
            allow_subdomains = "Yes" if domain.get("allowSubdomains") else "No"
            is_allowed = "Yes" if domain.get("isAllowed") else "No"

            table_data.append(
                [
                    rule_id,
                    fqdn,
                    is_allowed,
                    allow_subdomains,
                ]
            )

        self.logger.info(
            tabulate(
                table_data,
                headers=[
                    "Rule ID",
                    "Domain",
                    "Allowed",
                    "Rules applies to subdomains",
                ],
                tablefmt="grid",
            )
        )

    def _add_all_domains(self):
        """Add all available domains to an ACME account."""
        self.logger.info(f"Adding all available domains to account ID: {self.args.account_id}")
        res = self.harica_client.acme_allow_all_domains(self.args.account_id)

        if res:
            self.logger.info(
                f"{Fore.GREEN}Successfully added all available domains to account ID: {self.args.account_id}{Style.RESET_ALL}"
            )
            self.logger.info(f"Run 'tcs-garr acme domains {self.args.account_id} --active' to see the new domains")
        else:
            self.logger.error(
                f"{Fore.RED}Failed to add all available domains to account ID: {self.args.account_id}{Style.RESET_ALL}"
            )

    def _add_domain_rule(self):
        """Add a domain rule to an ACME account."""

        def is_subdomain_of(subdomain: str, domain: str) -> bool:
            """Check if subdomain is a subdomain of domain.

            Parameters
            ----------
            subdomain : str
                Subdomain
            domain : str
                Domain

            Returns
            -------
            bool
                True if subdomain is a subdomain of domain, False otherwise
            """
            # Quick sanity: domain must be non-empty and contain at least one dot
            if not domain or "." not in domain:
                return False

            # Also subdomain must contain at least one dot (not bare name)
            if "." not in subdomain:
                return False

            # Check if subdomain ends with "." + domain or equals domain
            # but to be subdomain it must be strictly longer and domain must be suffix after a dot
            if subdomain == domain:
                return False  # same domain is not a subdomain

            # Ensure subdomain ends with domain preceded by a dot
            if subdomain.endswith(f".{domain}"):
                return True

            return False

        self.logger.info(f"Adding domain '{self.args.domain}' to account ID: {self.args.account_id}")

        # Sanitize domains and check subdomains
        self.args.domain = self.args.domain.lower()

        if self.args.subdomain:
            self.args.subdomain = self.args.subdomain.lower()

            if not is_subdomain_of(self.args.subdomain, self.args.domain):
                self.logger.error(
                    f"{Fore.RED}Subdomain '{self.args.subdomain}' must be a subdomain of domain '{self.args.domain}'.{Style.RESET_ALL}"
                )
                return

        try:
            res = self.harica_client.create_acme_domain_rule(
                self.args.account_id,
                self.args.domain,
                subdomain=self.args.subdomain,
                applies_to_subdomains=self.args.no_subdomains,
                allowed=self.args.not_allowed,
            )

            target_domain = self.args.subdomain if self.args.subdomain else self.args.domain

            if res:
                self.logger.info(
                    f"{Fore.GREEN}Successfully added domain rule '{target_domain}' to account ID: {self.args.account_id}{Style.RESET_ALL}"
                )
                self.logger.info(f"Run 'tcs-garr acme domains {self.args.account_id} --active' to see the active domain rules")
            else:
                self.logger.error(
                    f"{Fore.RED}Failed to add domain rule '{target_domain}' to account ID: {self.args.account_id}{Style.RESET_ALL}"
                )
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to add domain rule: {e}{Style.RESET_ALL}")

    def _remove_domain_rule(self):
        """Remove a domain rule from an ACME account."""
        self.logger.info(f"Removing domain '{self.args.rule_id}' from account ID: {self.args.account_id}")

        try:
            res = self.harica_client.remove_acme_domain_rule(self.args.rule_id)

            if res:
                self.logger.info(
                    f"{Fore.GREEN}Successfully removed domain rule with ID: {self.args.rule_id} from account ID: {self.args.account_id}{Style.RESET_ALL}"
                )
                self.logger.info(
                    f"Run 'tcs-garr acme domains {self.args.account_id} --inactive' to see the inactive domain rules"
                )
            else:
                self.logger.error(
                    f"{Fore.RED}Failed to remove domain rule with ID: {self.args.rule_id} from account ID: {self.args.account_id}{Style.RESET_ALL}"
                )
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to remove domain rule: {e}{Style.RESET_ALL}")

    def _format_account_output(self, account, show_secrets=False) -> str:
        """Format an ACME account entry for display.

        Parameters
        ----------
        account : dict
            ACME account entry.
        show_secrets : bool, optional
            Whether to include secrets in the output, by default False

        Returns
        -------
        str
            Formatted output string.

        """
        id = account.get("id", "N/A")
        friendly_name = account.get("friendlyName", "N/A")
        email = account.get("userEmail", "N/A")
        entity = account.get("entityName", "N/A")
        acme_url = account.get("acmeServerUrl", "N/A")
        created_at = account.get("createdAt", "N/A")
        notes = account.get("notes", None)
        is_enabled = account.get("isEnabled", False)
        type = account.get("transactionTypeName", "N/A")
        last_time_used = account.get("lastTimeUsed", "N/A")

        try:
            created_str, _ = format_date_and_check_expiry(created_at)
        except ValueError:
            created_str = "Unknown"

        status_color = Fore.GREEN if is_enabled else Fore.RED
        status_str = "ENABLED" if is_enabled else "DISABLED"

        output = (
            f"{status_color}{friendly_name}{Style.RESET_ALL} "
            f"({entity}) - {email}\n"
            f"  ID: {id}\n"
            f"  Status: {status_color}{status_str}{Style.RESET_ALL}\n"
            f"  Type: {type}\n"
            f"  Created: {created_str}\n"
            f"  Last time used: {last_time_used}"
        )

        if notes:
            output += f"\n  Notes: {notes}"

        # Add acme url here
        output += f"\n  ACME URL: {acme_url}"

        if show_secrets:
            key_id = account.get("keyId", "N/A")
            hmac_key = account.get("hmacKey", "N/A")
            output += f"\n  Key ID: {key_id}\n  HMAC Key: {hmac_key}"

        return output

    def _list_accounts(self, show_disabled=False):
        """Lists all ACME accounts configured in Harica."""
        try:
            accounts = self.harica_client.list_acme_accounts()
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to retrieve ACME accounts: {e}{Style.RESET_ALL}")
            return

        if not accounts:
            self.logger.info(f"{Fore.YELLOW}No ACME accounts found.{Style.RESET_ALL}")
            return

        self.logger.info(f"{Fore.CYAN}ACME account(s):{Style.RESET_ALL}\n")

        count = {"enabled": 0, "disabled": 0}

        for account in accounts:
            enabled = account.get("isEnabled", False)
            count["disabled" if not enabled else "enabled"] += 1

            # Flag not provided and account is disabled
            if not show_disabled and not enabled:
                continue

            output = self._format_account_output(account)
            self.logger.info(f"{output}\n")

        self.logger.info(f"{Fore.CYAN}Summary:{Style.RESET_ALL}")
        self.logger.info(f"  Enabled: {Fore.GREEN}{count['enabled']}{Style.RESET_ALL}")
        self.logger.info(f"  Disabled: {Fore.RED}{count['disabled']}{Style.RESET_ALL}\n")

        self.logger.info(
            f"{Fore.CYAN}To see more details about an ACME account, use "
            f"the 'info' subcommand with the account ID.{Style.RESET_ALL}"
        )

        if not show_disabled:
            self.logger.info(
                f"{Fore.CYAN}To also display disabled ACME accounts, use "
                "the 'list' subcommand with the '--show-disabled' "
                f"flag.{Style.RESET_ALL}"
            )

    def _account_info(self, account_id: str):
        """Displays information about a specific ACME account.

        Parameters
        ----------
        account_id : str
            The ID of the ACME account.

        """
        try:
            accounts = self.harica_client.list_acme_accounts()
            account = next((a for a in accounts if a.get("id") == account_id), None)
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to retrieve ACME accounts: {e}{Style.RESET_ALL}")
            return

        if not account:
            self.logger.error(f"{Fore.RED}ACME account not found.{Style.RESET_ALL}")
            return

        output = self._format_account_output(account, show_secrets=True)
        self.logger.info(f"{output}\n")

    def _create_account(self, friendly_name: str, transaction_type: str):
        """Creates a new ACME account.

        Parameters
        ----------
        friendly_name : str
            Friendly name for the ACME account.
        transaction_type : str
            Type of ACME transaction (e.g., "SSL DV" or "SSL OV")

        """
        try:
            account = self.harica_client.create_acme_account(friendly_name=friendly_name, transaction_type=transaction_type)

            if not account:
                self.logger.error(f"{Fore.RED}Failed to create ACME account.{Style.RESET_ALL}")
                return

            self.logger.info(f"{Fore.GREEN}ACME account created successfully:{Style.RESET_ALL}\n")

            output = self._format_account_output(account, show_secrets=True)
            self.logger.info(f"{output}\n")
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to create ACME account: {e}{Style.RESET_ALL}")

    def _disable_account(self):
        """Disable an ACME account."""
        try:
            res = self.harica_client.disable_acme_account(self.args.account_id)

            if res:
                self.logger.info(
                    f"{Fore.GREEN}Successfully disabled ACME account with ID: {self.args.account_id}{Style.RESET_ALL}"
                )
            else:
                self.logger.error(f"{Fore.RED}Failed to disable ACME account with ID: {self.args.account_id}{Style.RESET_ALL}")
        except Exception as e:
            self.logger.error(f"{Fore.RED}Failed to disable ACME account: {e}{Style.RESET_ALL}")
