"""Namespace infrastructure CRD models for Citadel.

Maps the citadel.dev/v1alpha1 NamespaceInfrastructure CRD to Python dataclasses.
These models define the infrastructure resources (network policies, quotas,
limits) that Citadel provisions for each namespace.
"""

from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any


class InfraPhase(str, Enum):
    """Lifecycle phase of a NamespaceInfrastructure resource."""

    PENDING = "Pending"
    PROVISIONING = "Provisioning"
    ACTIVE = "Active"
    FAILED = "Failed"


@dataclass(frozen=True)
class NetworkPolicyConfig:
    """Network policy configuration for namespace infrastructure."""

    default_deny: bool = True
    ingress_rules: tuple[dict[str, Any], ...] = ()
    egress_rules: tuple[dict[str, Any], ...] = ()
    allowed_peers: tuple[str, ...] = ()
    allow_kube_system: bool = True
    allow_monitoring: bool = True

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "NetworkPolicyConfig":
        return cls(
            default_deny=data.get("defaultDeny", True),
            ingress_rules=tuple(data.get("ingressRules", [])),
            egress_rules=tuple(data.get("egressRules", [])),
            allowed_peers=tuple(data.get("allowedPeers", [])),
            allow_kube_system=data.get("allowKubeSystem", True),
            allow_monitoring=data.get("allowMonitoring", True),
        )

    def to_dict(self) -> dict[str, Any]:
        return {
            "defaultDeny": self.default_deny,
            "ingressRules": list(self.ingress_rules),
            "egressRules": list(self.egress_rules),
            "allowedPeers": list(self.allowed_peers),
            "allowKubeSystem": self.allow_kube_system,
            "allowMonitoring": self.allow_monitoring,
        }


@dataclass(frozen=True)
class ResourceQuotaConfig:
    """Resource quota limits for the namespace."""

    cpu: str | None = None
    memory: str | None = None
    pods: str | None = None
    storage: str | None = None
    pvcs: str | None = None
    custom: tuple[tuple[str, str], ...] = ()

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "ResourceQuotaConfig":
        custom = tuple(data.get("custom", {}).items())
        return cls(
            cpu=data.get("cpu"),
            memory=data.get("memory"),
            pods=data.get("pods"),
            storage=data.get("storage"),
            pvcs=data.get("pvcs"),
            custom=custom,
        )

    def to_dict(self) -> dict[str, Any]:
        d: dict[str, Any] = {}
        if self.cpu is not None:
            d["cpu"] = self.cpu
        if self.memory is not None:
            d["memory"] = self.memory
        if self.pods is not None:
            d["pods"] = self.pods
        if self.storage is not None:
            d["storage"] = self.storage
        if self.pvcs is not None:
            d["pvcs"] = self.pvcs
        if self.custom:
            d["custom"] = dict(self.custom)
        return d


@dataclass(frozen=True)
class LimitRangeConfig:
    """Default and maximum resource limits for containers."""

    default_cpu: str = "500m"
    default_memory: str = "512Mi"
    default_request_cpu: str = "100m"
    default_request_memory: str = "128Mi"
    max_cpu: str = "4"
    max_memory: str = "8Gi"

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "LimitRangeConfig":
        return cls(
            default_cpu=data.get("defaultCpu", "500m"),
            default_memory=data.get("defaultMemory", "512Mi"),
            default_request_cpu=data.get("defaultRequestCpu", "100m"),
            default_request_memory=data.get("defaultRequestMemory", "128Mi"),
            max_cpu=data.get("maxCpu", "4"),
            max_memory=data.get("maxMemory", "8Gi"),
        )

    def to_dict(self) -> dict[str, Any]:
        return {
            "defaultCpu": self.default_cpu,
            "defaultMemory": self.default_memory,
            "defaultRequestCpu": self.default_request_cpu,
            "defaultRequestMemory": self.default_request_memory,
            "maxCpu": self.max_cpu,
            "maxMemory": self.max_memory,
        }


@dataclass(frozen=True)
class NamespaceInfraSpec:
    """Spec for a NamespaceInfrastructure CRD."""

    target_namespace: str
    network_policy: NetworkPolicyConfig = field(default_factory=NetworkPolicyConfig)
    resource_quota: ResourceQuotaConfig | None = None
    limit_range: LimitRangeConfig | None = None

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "NamespaceInfraSpec":
        np_data = data.get("networkPolicy", {})
        rq_data = data.get("resourceQuota")
        lr_data = data.get("limitRange")

        return cls(
            target_namespace=data["targetNamespace"],
            network_policy=NetworkPolicyConfig.from_dict(np_data),
            resource_quota=ResourceQuotaConfig.from_dict(rq_data) if rq_data else None,
            limit_range=LimitRangeConfig.from_dict(lr_data) if lr_data else None,
        )

    def to_dict(self) -> dict[str, Any]:
        d: dict[str, Any] = {
            "targetNamespace": self.target_namespace,
            "networkPolicy": self.network_policy.to_dict(),
        }
        if self.resource_quota is not None:
            d["resourceQuota"] = self.resource_quota.to_dict()
        if self.limit_range is not None:
            d["limitRange"] = self.limit_range.to_dict()
        return d


@dataclass
class ManagedInfraResources:
    """Tracks which infrastructure resources have been created."""

    default_deny_policy: bool = False
    tenant_policy: bool = False
    resource_quota: bool = False
    limit_range: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "defaultDenyPolicy": self.default_deny_policy,
            "tenantPolicy": self.tenant_policy,
            "resourceQuota": self.resource_quota,
            "limitRange": self.limit_range,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "ManagedInfraResources":
        return cls(
            default_deny_policy=data.get("defaultDenyPolicy", False),
            tenant_policy=data.get("tenantPolicy", False),
            resource_quota=data.get("resourceQuota", False),
            limit_range=data.get("limitRange", False),
        )


@dataclass
class InfraCondition:
    """A condition observation for the NamespaceInfrastructure status."""

    type: str
    status: str
    message: str = ""
    reason: str = ""
    last_transition_time: datetime = field(default_factory=lambda: datetime.now(UTC))

    def to_dict(self) -> dict[str, Any]:
        return {
            "type": self.type,
            "status": self.status,
            "message": self.message,
            "reason": self.reason,
            "lastTransitionTime": self.last_transition_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "InfraCondition":
        ltt = data.get("lastTransitionTime")
        return cls(
            type=data["type"],
            status=data["status"],
            message=data.get("message", ""),
            reason=data.get("reason", ""),
            last_transition_time=(
                datetime.fromisoformat(ltt.rstrip("Z")) if ltt else datetime.now(UTC)
            ),
        )


@dataclass
class NamespaceInfraStatus:
    """Observed state of a NamespaceInfrastructure resource."""

    phase: InfraPhase = InfraPhase.PENDING
    ready: bool = False
    managed_resources: ManagedInfraResources = field(default_factory=ManagedInfraResources)
    conditions: list[InfraCondition] = field(default_factory=list)
    policy_hash: str | None = None

    def to_dict(self) -> dict[str, Any]:
        d: dict[str, Any] = {
            "phase": self.phase.value,
            "ready": self.ready,
            "managedResources": self.managed_resources.to_dict(),
            "conditions": [c.to_dict() for c in self.conditions],
        }
        if self.policy_hash is not None:
            d["policyHash"] = self.policy_hash
        return d

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "NamespaceInfraStatus":
        return cls(
            phase=InfraPhase(data.get("phase", "Pending")),
            ready=data.get("ready", False),
            managed_resources=ManagedInfraResources.from_dict(
                data.get("managedResources", {})
            ),
            conditions=[
                InfraCondition.from_dict(c) for c in data.get("conditions", [])
            ],
            policy_hash=data.get("policyHash"),
        )


@dataclass
class NamespaceInfrastructure:
    """Complete NamespaceInfrastructure CRD representation."""

    name: str
    namespace: str
    spec: NamespaceInfraSpec
    status: NamespaceInfraStatus = field(default_factory=NamespaceInfraStatus)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "NamespaceInfrastructure":
        metadata = data.get("metadata", {})
        return cls(
            name=metadata.get("name", ""),
            namespace=metadata.get("namespace", "default"),
            spec=NamespaceInfraSpec.from_dict(data.get("spec", {})),
            status=NamespaceInfraStatus.from_dict(data.get("status", {})),
        )
