#!/usr/bin/env python3
"""Service account module."""

import os
import subprocess
from argparse import ArgumentParser, Namespace
from enum import Enum
from logging import Logger

from lightkube import ApiError

from spark8t.cli import defaults
from spark8t.cli.params import (
    add_config_arguments,
    add_logging_arguments,
    k8s_parser,
    parse_arguments_with,
    spark_user_parser,
)
from spark8t.domain import KubernetesResourceType, ServiceAccount
from spark8t.exceptions import (
    AccountNotFound,
    NamespaceNotFound,
    PrimaryAccountNotFound,
    ResourceAlreadyExists,
)
from spark8t.kube_interface.base import AbstractKubeInterface
from spark8t.kube_interface.lightkube import LightKubeInterface
from spark8t.registry.k8s import K8sServiceAccountRegistry
from spark8t.utils import PropertyFile, setup_logging


def build_service_account_from_args(args, registry) -> ServiceAccount:
    """Create service account resource interface."""
    return ServiceAccount(
        name=os.path.expandvars(args.username),
        namespace=os.path.expandvars(args.namespace),
        api_server=registry.kube_interface.api_server,
        primary=args.primary if hasattr(args, "primary") else False,
    )


class Actions(str, Enum):
    """Service account CLI action."""

    CREATE = "create"
    DELETE = "delete"
    ADD_CONFIG = "add-config"
    REMOVE_CONFIG = "remove-config"
    GET_CONFIG = "get-config"
    CLEAR_CONFIG = "clear-config"
    PRIMARY = "get-primary"
    LIST = "list"
    GET_MANIFEST = "get-manifest"

    def __str__(self) -> str:
        """Define string representation.

        TODO(py310): replace inheritance with StrEnum once we drop py310
        """
        return str.__str__(self)


def create_namespace_if_missing(kube_interface: AbstractKubeInterface, namespace: str):
    """Create namespace if does not exist."""
    if not kube_interface.exists(KubernetesResourceType.NAMESPACE, namespace):
        try:
            kube_interface.create(KubernetesResourceType.NAMESPACE, namespace)
        except ApiError as e:
            if e.status.code == 401 or e.status.code == 403:
                print(f"Namespace {namespace} can not be created.")
                raise NamespaceNotFound(namespace) from None
            else:
                raise e
        except subprocess.CalledProcessError as err:
            print(f"Namespace {namespace} can not be created.")
            raise NamespaceNotFound(namespace) from err


def create_service_account_registry_parser(parser: ArgumentParser):
    """Create parser for service account CLI."""
    base_parser = parse_arguments_with(
        [add_logging_arguments, k8s_parser],
        ArgumentParser(add_help=False),
    )

    subparsers = parser.add_subparsers(dest="action")
    subparsers.required = True

    #  subparser for service-account
    parse_arguments_with(
        [add_config_arguments, spark_user_parser],
        subparsers.add_parser(Actions.CREATE.value, parents=[base_parser]),
    ).add_argument(
        "--primary",
        action="store_true",
        help="Boolean to mark the service account as primary.",
    )

    #  subparser for service-account-cleanup
    parse_arguments_with(
        [spark_user_parser],
        subparsers.add_parser(Actions.DELETE.value, parents=[base_parser]),
    )

    #  subparser for add-config
    parse_arguments_with(
        [add_config_arguments, spark_user_parser],
        subparsers.add_parser(Actions.ADD_CONFIG.value, parents=[base_parser]),
    )

    #  subparser for remove-config
    parse_arguments_with(
        [add_config_arguments, spark_user_parser],
        subparsers.add_parser(Actions.REMOVE_CONFIG.value, parents=[base_parser]),
    )

    #  subparser for sa-conf-get
    parse_arguments_with(
        [spark_user_parser],
        subparsers.add_parser(Actions.GET_CONFIG.value, parents=[base_parser]),
    ).add_argument(
        "--ignore-integration-hub",
        action="store_true",
        help="Boolean to ignore Spark Integration Hub generated options.",
    )

    #  subparser for get-manifest
    parse_arguments_with(
        [spark_user_parser],
        subparsers.add_parser(Actions.GET_MANIFEST.value, parents=[base_parser]),
    )

    #  subparser for sa-conf-del
    parse_arguments_with(
        [spark_user_parser],
        subparsers.add_parser(Actions.CLEAR_CONFIG.value, parents=[base_parser]),
    )

    #  subparser for resources-primary-sa
    subparsers.add_parser(Actions.PRIMARY.value, parents=[base_parser])

    #  subparser for list
    subparsers.add_parser(Actions.LIST.value, parents=[base_parser])

    return parser


def main(args: Namespace, logger: Logger):
    """Service account main entrypoint."""
    kubeconfig = os.path.expandvars(args.kubeconfig) if args.kubeconfig else None
    context_name = os.path.expandvars(args.context) if args.context else None

    kube_interface = LightKubeInterface(
        kubeconfig or defaults.kube_config, defaults, context_name=context_name
    )
    context = context_name or kube_interface.context_name

    logger.debug(f"Using K8s context: {context}")

    registry = K8sServiceAccountRegistry(
        kube_interface.with_context(context) if context else kube_interface
    )

    if args.action == Actions.CREATE:
        service_account = build_service_account_from_args(args, registry)

        # check if namespace exist otherwise create it if permissions allow it.
        create_namespace_if_missing(kube_interface, service_account.namespace)
        properties_file = (
            os.path.expandvars(args.properties_file) if args.properties_file else None
        )
        service_account.extra_confs = (
            PropertyFile.read(properties_file)
            if properties_file is not None
            else PropertyFile.empty()
        ) + PropertyFile.parse_conf_overrides(args.conf)
        registry.create(service_account)

    elif args.action == Actions.GET_MANIFEST:
        service_account = build_service_account_from_args(args, registry)
        manifest = registry.create(service_account, dry_run=True)
        print(manifest)

    elif args.action == Actions.DELETE:
        user_id = build_service_account_from_args(args, registry).id
        logger.info(user_id)
        registry.delete(user_id)

    elif args.action == Actions.ADD_CONFIG:
        input_service_account = build_service_account_from_args(args, registry)

        service_account_in_registry = registry.get(input_service_account.id)

        if service_account_in_registry is None:
            raise AccountNotFound(input_service_account.id)
        properties_file = (
            os.path.expandvars(args.properties_file) if args.properties_file else None
        )
        account_configuration = (
            service_account_in_registry.extra_confs
            + (
                PropertyFile.read(properties_file)
                if properties_file is not None
                else PropertyFile.empty()
            )
            + PropertyFile.parse_conf_overrides(args.conf)
        )

        registry.set_configurations(input_service_account.id, account_configuration)

    elif args.action == Actions.REMOVE_CONFIG:
        input_service_account = build_service_account_from_args(args, registry)

        service_account_in_registry = registry.get(input_service_account.id)

        if service_account_in_registry is None:
            raise AccountNotFound(input_service_account.id)

        registry.set_configurations(
            input_service_account.id,
            service_account_in_registry.extra_confs.remove(args.conf),
        )

    elif args.action == Actions.GET_CONFIG:
        input_service_account = build_service_account_from_args(args, registry)

        maybe_service_account = registry.get(input_service_account.id)

        if maybe_service_account is None:
            raise AccountNotFound(input_service_account.id)

        if args.ignore_integration_hub:
            maybe_service_account.integration_hub_confs = PropertyFile.empty()
        maybe_service_account.configurations.log(print)

    elif args.action == Actions.CLEAR_CONFIG:
        registry.set_configurations(
            build_service_account_from_args(args, registry).id, PropertyFile.empty()
        )

    elif args.action == Actions.PRIMARY:
        maybe_service_account = registry.get_primary()

        if maybe_service_account is None:
            raise PrimaryAccountNotFound()

        print(maybe_service_account.id)

    elif args.action == Actions.LIST:
        for service_account in registry.all():
            print_line = f"{service_account.id}"
            if service_account.primary:
                print_line += " (Primary)"
            print(print_line)


if __name__ == "__main__":
    args = create_service_account_registry_parser(
        ArgumentParser(description="Spark Client Setup")
    ).parse_args()

    logger = setup_logging(
        args.log_level, args.log_conf_file, "spark8t.cli.service_account_registry"
    )

    try:
        main(args, logger)
        exit(0)
    except (AccountNotFound, PrimaryAccountNotFound, ResourceAlreadyExists) as e:
        print(str(e))
        exit(1)
    except Exception as e:
        raise e
