"""KuberokuFactory — dependency wiring for SDK services.

Created once per CLI invocation. Tests create their own with a FakeK8sClient.
Every SDK service receives this factory (and only this factory) in its constructor.
"""

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any

from kuberoku import branding
from kuberoku.config.user import get_cluster, get_current_cluster_name
from kuberoku.k8s.client import K8sClient
from kuberoku.sdk.addons import AddonsService
from kuberoku.sdk.apps import AppsService
from kuberoku.sdk.build import BuildService
from kuberoku.sdk.clusters import ClustersService
from kuberoku.sdk.config import ConfigService
from kuberoku.sdk.deploy import DeployService
from kuberoku.sdk.doctor import DoctorService
from kuberoku.sdk.domains import DomainsService
from kuberoku.sdk.gateway import GatewayService
from kuberoku.sdk.plugins import PluginService
from kuberoku.sdk.ps import PsService
from kuberoku.sdk.registry import RegistryService
from kuberoku.sdk.releases import ReleasesService
from kuberoku.sdk.services import ServicesService

if TYPE_CHECKING:
    from kuberoku.k8s.protocols import K8sClientProtocol
    from kuberoku.models import ClusterConfig


class KuberokuFactory:
    """Wires dependencies for SDK services.

    In production (CLI): KuberokuFactory()  — lazy, resolves from config/env.
    In tests:            KuberokuFactory(k8s_client=FakeK8sClient(), namespace="test")
    """

    def __init__(
        self,
        k8s_client: K8sClientProtocol | None = None,
        namespace: str | None = None,
        resource_prefix: str | None = None,
        label_domain: str | None = None,
        cluster: str | None = None,
    ) -> None:
        self._k8s_client = k8s_client
        self._namespace = namespace
        self._resource_prefix = resource_prefix
        self._label_domain = label_domain
        self._cluster = cluster
        self._cluster_config: ClusterConfig | None = None
        self._cluster_resolved = k8s_client is not None  # Skip resolution for tests
        self._services: dict[str, Any] = {}

    def _resolve_cluster_config(self) -> ClusterConfig | None:
        """Resolve cluster config from the config chain.

        Resolution order:
        1. Explicit cluster parameter
        2. KUBEROKU_CLUSTER env var
        3. ~/.kuberoku/config.yaml current_cluster
        4. None (fall back to kubeconfig default)
        """
        if self._cluster_resolved:
            return self._cluster_config

        self._cluster_resolved = True

        cluster_name = self._cluster or os.environ.get(f"{branding.ENV_PREFIX}CLUSTER")

        if cluster_name is None:
            cluster_name = get_current_cluster_name()

        if cluster_name is None:
            return None

        self._cluster_config = get_cluster(cluster_name)
        return self._cluster_config

    @property
    def k8s(self) -> K8sClientProtocol:
        """Lazy — only connects to K8s when first accessed."""
        if self._k8s_client is None:
            cluster_cfg = self._resolve_cluster_config()
            context = cluster_cfg.context if cluster_cfg else None
            self._k8s_client = K8sClient.from_context(context)
        return self._k8s_client

    @property
    def namespace(self) -> str:
        if self._namespace is None:
            # Check env var first
            env_ns = os.environ.get(f"{branding.ENV_PREFIX}NAMESPACE")
            if env_ns:
                self._namespace = env_ns
            else:
                cluster_cfg = self._resolve_cluster_config()
                if cluster_cfg and cluster_cfg.namespace:
                    self._namespace = cluster_cfg.namespace
                else:
                    self._namespace = branding.DEFAULT_NAMESPACE
        return self._namespace

    @property
    def prefix(self) -> str:
        if self._resource_prefix:
            return self._resource_prefix
        cluster_cfg = self._resolve_cluster_config()
        if cluster_cfg and cluster_cfg.resource_prefix:
            return cluster_cfg.resource_prefix
        return branding.DEFAULT_RESOURCE_PREFIX

    @property
    def domain(self) -> str:
        if self._label_domain:
            return self._label_domain
        cluster_cfg = self._resolve_cluster_config()
        if cluster_cfg and cluster_cfg.label_domain:
            return cluster_cfg.label_domain
        return branding.DEFAULT_LABEL_DOMAIN

    # ── SDK service properties ───────────────────────────────────

    @property
    def addons(self) -> AddonsService:
        if "addons" not in self._services:
            self._services["addons"] = AddonsService(self)
        svc: AddonsService = self._services["addons"]
        return svc

    @property
    def apps(self) -> AppsService:
        if "apps" not in self._services:
            self._services["apps"] = AppsService(self)
        svc: AppsService = self._services["apps"]
        return svc

    @property
    def config(self) -> ConfigService:
        if "config" not in self._services:
            self._services["config"] = ConfigService(self)
        svc: ConfigService = self._services["config"]
        return svc

    @property
    def clusters(self) -> ClustersService:
        if "clusters" not in self._services:
            self._services["clusters"] = ClustersService(self)
        svc: ClustersService = self._services["clusters"]
        return svc

    @property
    def doctor(self) -> DoctorService:
        if "doctor" not in self._services:
            self._services["doctor"] = DoctorService(self)
        svc: DoctorService = self._services["doctor"]
        return svc

    @property
    def deploy(self) -> DeployService:
        if "deploy" not in self._services:
            self._services["deploy"] = DeployService(self)
        svc: DeployService = self._services["deploy"]
        return svc

    @property
    def releases(self) -> ReleasesService:
        if "releases" not in self._services:
            self._services["releases"] = ReleasesService(self)
        svc: ReleasesService = self._services["releases"]
        return svc

    @property
    def ps(self) -> PsService:
        if "ps" not in self._services:
            self._services["ps"] = PsService(self)
        svc: PsService = self._services["ps"]
        return svc

    @property
    def build(self) -> BuildService:
        if "build" not in self._services:
            self._services["build"] = BuildService(self)
        svc: BuildService = self._services["build"]
        return svc

    @property
    def services(self) -> ServicesService:
        if "services" not in self._services:
            self._services["services"] = ServicesService(self)
        svc: ServicesService = self._services["services"]
        return svc

    @property
    def registry(self) -> RegistryService:
        if "registry" not in self._services:
            self._services["registry"] = RegistryService(self)
        svc: RegistryService = self._services["registry"]
        return svc

    @property
    def domains(self) -> DomainsService:
        if "domains" not in self._services:
            self._services["domains"] = DomainsService(self)
        svc: DomainsService = self._services["domains"]
        return svc

    @property
    def plugins(self) -> PluginService:
        if "plugins" not in self._services:
            self._services["plugins"] = PluginService(self)
        svc: PluginService = self._services["plugins"]
        return svc

    @property
    def gateway(self) -> GatewayService:
        if "gateway" not in self._services:
            self._services["gateway"] = GatewayService(self)
        svc: GatewayService = self._services["gateway"]
        return svc

    @property
    def base_domain(self) -> str | None:
        """Base domain for auto-domain (e.g., 'apps.mycluster.com')."""
        cluster_cfg = self._resolve_cluster_config()
        if cluster_cfg and cluster_cfg.base_domain:
            return cluster_cfg.base_domain
        return os.environ.get(f"{branding.ENV_PREFIX}BASE_DOMAIN")

    # ── Namespace management ─────────────────────────────────────

    def ensure_namespace(self) -> None:
        """Create the namespace if it doesn't exist. Handle 403 gracefully."""
        if self.k8s.namespace_exists(self.namespace):
            return
        try:
            self.k8s.create_namespace(self.namespace)
        except Exception:
            # Tolerate if namespace appeared (race) or if we lack create
            # permission but the namespace already exists
            if not self.k8s.namespace_exists(self.namespace):
                raise
