"""Node lifecycle CRD models for Citadel.

Maps the citadel.dev/v1alpha1 NodeTerminationPolicy CRD to Python dataclasses.
Ported from Apollo's spoke/node_lifecycle/models/policy.py — Citadel now owns
these models since it owns the operator that reconciles them.
"""

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any


@dataclass(frozen=True)
class ConditionMatch:
    """A Kubernetes node condition to match against."""

    type: str
    """Condition type (e.g., DiskPressure, MemoryPressure, Ready)."""

    status: str
    """Condition status to match (True, False, Unknown)."""


@dataclass(frozen=True)
class DrainConfig:
    """Configuration for the drain operation."""

    timeout_seconds: int = 300
    """Maximum time to wait for pod eviction."""

    delete_emptydir_data: bool = True
    """Whether to evict pods using emptyDir volumes."""

    force_after_timeout: int | None = None
    """Seconds after which to force-delete pods that couldn't be evicted."""


@dataclass(frozen=True)
class PolicySelector:
    """Criteria for selecting nodes to terminate."""

    max_age_hours: float | None = None
    """Terminate nodes older than this many hours."""

    conditions: list[ConditionMatch] = field(default_factory=list)
    """Terminate nodes matching these Kubernetes conditions."""

    node_selector: dict[str, str] = field(default_factory=dict)
    """Match nodes by label selector."""

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "PolicySelector":
        conditions = [
            ConditionMatch(type=c["type"], status=c["status"])
            for c in data.get("conditions", [])
        ]
        return cls(
            max_age_hours=data.get("maxAgeHours"),
            conditions=conditions,
            node_selector=data.get("nodeSelector", {}),
        )


@dataclass
class PolicySpec:
    """Specification for a NodeTerminationPolicy."""

    reason: str
    """Human-readable reason for termination."""

    selector: PolicySelector
    """Criteria for selecting nodes."""

    priority: int = 10
    """Higher priority policies are evaluated first."""

    enabled: bool = True
    """Whether this policy is active."""

    dry_run: bool = False
    """If true, label but don't drain."""

    drain: DrainConfig = field(default_factory=DrainConfig)
    """Drain configuration."""

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "PolicySpec":
        selector = PolicySelector.from_dict(data.get("selector", {}))

        drain_data = data.get("drain", {})
        drain = DrainConfig(
            timeout_seconds=drain_data.get("timeoutSeconds", 300),
            delete_emptydir_data=drain_data.get("deleteEmptydirData", True),
            force_after_timeout=drain_data.get("forceAfterTimeoutSeconds"),
        )

        return cls(
            reason=data["reason"],
            selector=selector,
            priority=data.get("priority", 10),
            enabled=data.get("enabled", True),
            dry_run=data.get("dryRun", False),
            drain=drain,
        )


@dataclass
class PolicyStatus:
    """Status of a NodeTerminationPolicy."""

    last_evaluated: datetime | None = None
    nodes_matched: int = 0
    nodes_terminated: int = 0
    last_termination: datetime | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "lastEvaluated": (
                self.last_evaluated.isoformat() + "Z" if self.last_evaluated else None
            ),
            "nodesMatched": self.nodes_matched,
            "nodesTerminated": self.nodes_terminated,
            "lastTermination": (
                self.last_termination.isoformat() + "Z" if self.last_termination else None
            ),
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "PolicyStatus":
        return cls(
            last_evaluated=(
                datetime.fromisoformat(data["lastEvaluated"].rstrip("Z"))
                if data.get("lastEvaluated")
                else None
            ),
            nodes_matched=data.get("nodesMatched", 0),
            nodes_terminated=data.get("nodesTerminated", 0),
            last_termination=(
                datetime.fromisoformat(data["lastTermination"].rstrip("Z"))
                if data.get("lastTermination")
                else None
            ),
        )


@dataclass
class NodeTerminationPolicy:
    """Python representation of NodeTerminationPolicy CRD."""

    name: str
    """Policy name from metadata."""

    spec: PolicySpec
    """Policy specification."""

    status: PolicyStatus = field(default_factory=PolicyStatus)
    """Policy status."""

    labels: dict[str, str] = field(default_factory=dict)
    """Metadata labels."""

    @property
    def policy_type(self) -> str:
        """Infer policy type from labels or selector."""
        label_type = self.labels.get("citadel.dev/policy-type")
        if label_type:
            return label_type
        if self.spec.selector.max_age_hours is not None:
            return "age"
        if self.spec.selector.conditions:
            return "health"
        return "custom"

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "NodeTerminationPolicy":
        metadata = data.get("metadata", {})
        spec = PolicySpec.from_dict(data.get("spec", {}))
        status = PolicyStatus.from_dict(data.get("status", {}))

        return cls(
            name=metadata.get("name", ""),
            spec=spec,
            status=status,
            labels=metadata.get("labels", {}),
        )


@dataclass(frozen=True)
class PolicyMatch:
    """A node matched by a termination policy."""

    node_name: str
    policy_name: str
    reason: str
    priority: int
    age_hours: float
    dry_run: bool
    drain_timeout: int
    delete_emptydir_data: bool = True
    force_after_timeout: int | None = None


@dataclass
class CycleResult:
    """Result of a full evaluation cycle."""

    policies_evaluated: int
    nodes_checked: int
    nodes_matched: int
    nodes_drained: int
    nodes_failed: list[str] = field(default_factory=list)
    dry_run_only: list[str] = field(default_factory=list)


@dataclass
class LifecycleConfig:
    """Configuration for the lifecycle operator."""

    kubeconfig: str | None = None
    """Path to kubeconfig file. None = auto-detect (in-cluster or default)."""

    evaluation_interval_seconds: int = 60
    """How often to evaluate policies (used by the kopf timer)."""

    dry_run_override: bool = False
    """If true, force all policies into dry-run mode."""

    max_age_hours: float = 48.0
    """Default max age for health assessment."""
