"""Kubernetes deployment controller"""

from io import StringIO

from kubernetes.client import VersionApi, VersionInfo
from kubernetes.client.api_client import ApiClient
from kubernetes.client.configuration import Configuration
from kubernetes.client.exceptions import OpenApiException
from kubernetes.config.config_exception import ConfigException
from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict
from urllib3.exceptions import HTTPError
from yaml import dump_all

from authentik.events.logs import LogEvent, capture_logs
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.secret import SecretReconciler
from authentik.outposts.controllers.k8s.service import MetricsServiceReconciler, ServiceReconciler
from authentik.outposts.controllers.k8s.service_monitor import PrometheusServiceMonitorReconciler
from authentik.outposts.models import (
    KubernetesServiceConnection,
    Outpost,
    OutpostServiceConnectionState,
    ServiceConnectionInvalid,
)


class KubernetesClient(ApiClient, BaseClient):
    """Custom kubernetes client based on service connection"""

    def __init__(self, connection: KubernetesServiceConnection):
        config = Configuration()
        try:
            if connection.local:
                load_incluster_config(client_configuration=config)
            else:
                load_kube_config_from_dict(connection.kubeconfig, client_configuration=config)
            config.verify_ssl = connection.verify_ssl
            super().__init__(config)
        except ConfigException as exc:
            raise ServiceConnectionInvalid(exc) from exc

    def fetch_state(self) -> OutpostServiceConnectionState:
        """Get version info"""
        try:
            api_instance = VersionApi(self)
            version: VersionInfo = api_instance.get_code()
            return OutpostServiceConnectionState(version=version.git_version, healthy=True)
        except OpenApiException, HTTPError, ServiceConnectionInvalid:
            return OutpostServiceConnectionState(version="", healthy=False)


class KubernetesController(BaseController):
    """Manage deployment of outpost in kubernetes"""

    reconcilers: dict[str, type[KubernetesObjectReconciler]]
    reconcile_order: list[str]

    client: KubernetesClient
    connection: KubernetesServiceConnection

    def __init__(
        self,
        outpost: Outpost,
        connection: KubernetesServiceConnection,
        client: KubernetesClient | None = None,
    ) -> None:
        super().__init__(outpost, connection)
        self.client = client if client else KubernetesClient(connection)
        self.reconcilers = {
            SecretReconciler.reconciler_name(): SecretReconciler,
            DeploymentReconciler.reconciler_name(): DeploymentReconciler,
            ServiceReconciler.reconciler_name(): ServiceReconciler,
            MetricsServiceReconciler.reconciler_name(): MetricsServiceReconciler,
            PrometheusServiceMonitorReconciler.reconciler_name(): (
                PrometheusServiceMonitorReconciler
            ),
        }
        self.reconcile_order = [
            SecretReconciler.reconciler_name(),
            DeploymentReconciler.reconciler_name(),
            ServiceReconciler.reconciler_name(),
            MetricsServiceReconciler.reconciler_name(),
            PrometheusServiceMonitorReconciler.reconciler_name(),
        ]

    def up(self):
        try:
            for reconcile_key in self.reconcile_order:
                reconciler_cls = self.reconcilers.get(reconcile_key)
                if not reconciler_cls:
                    continue
                reconciler = reconciler_cls(self)
                reconciler.up()

        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
            raise ControllerException(str(exc)) from exc

    def up_with_logs(self) -> list[LogEvent]:
        try:
            all_logs = []
            for reconcile_key in self.reconcile_order:
                if reconcile_key in self.outpost.config.kubernetes_disabled_components:
                    all_logs.append(
                        LogEvent(
                            log_level="info",
                            event=f"{reconcile_key.title()}: Disabled",
                            logger=class_to_path(self.__class__),
                        )
                    )
                    continue
                with capture_logs() as logs:
                    reconciler_cls = self.reconcilers.get(reconcile_key)
                    if not reconciler_cls:
                        continue
                    reconciler = reconciler_cls(self)
                    reconciler.up()
                for log in logs:
                    log.logger = reconcile_key.title()
                all_logs.extend(logs)
            return all_logs
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
            raise ControllerException(str(exc)) from exc

    def down(self):
        try:
            for reconcile_key in self.reconcile_order:
                reconciler_cls = self.reconcilers.get(reconcile_key)
                if not reconciler_cls:
                    continue
                reconciler = reconciler_cls(self)
                self.logger.debug("Tearing down object", name=reconcile_key)
                reconciler.down()

        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
            raise ControllerException(str(exc)) from exc

    def down_with_logs(self) -> list[LogEvent]:
        try:
            all_logs = []
            for reconcile_key in self.reconcile_order:
                if reconcile_key in self.outpost.config.kubernetes_disabled_components:
                    all_logs.append(
                        LogEvent(
                            log_level="info",
                            event=f"{reconcile_key.title()}: Disabled",
                            logger=class_to_path(self.__class__),
                        )
                    )
                    continue
                with capture_logs() as logs:
                    reconciler_cls = self.reconcilers.get(reconcile_key)
                    if not reconciler_cls:
                        continue
                    reconciler = reconciler_cls(self)
                    reconciler.down()
                for log in logs:
                    log.logger = reconcile_key.title()
                all_logs.extend(logs)
            return all_logs
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
            raise ControllerException(str(exc)) from exc

    def get_static_deployment(self) -> str:
        documents = []
        for reconcile_key in self.reconcile_order:
            reconciler_cls = self.reconcilers.get(reconcile_key)
            if not reconciler_cls:
                continue
            reconciler = reconciler_cls(self)
            if reconciler.noop:
                continue
            documents.append(reconciler.get_reference_object().to_dict())

        with StringIO() as _str:
            dump_all(
                documents,
                stream=_str,
                default_flow_style=False,
            )
            return _str.getvalue()
