"""Pure functions for generating Kubernetes resource manifests.

No I/O — every function returns a dict or list[dict] suitable for
the kubernetes Python client or YAML serialisation.
"""

from __future__ import annotations

import importlib.resources
from pathlib import Path
from typing import Any

import yaml

# ---------------------------------------------------------------------------
# Labels applied to every resource created by the CLI
# ---------------------------------------------------------------------------
_COMMON_LABELS: dict[str, str] = {
    "app.kubernetes.io/name": "citadel-operator",
    "app.kubernetes.io/part-of": "citadel",
    "app.kubernetes.io/managed-by": "citadel-cli",
}

# ---------------------------------------------------------------------------
# Namespace
# ---------------------------------------------------------------------------


def build_namespace(name: str = "citadel-system") -> dict[str, Any]:
    """Return a v1/Namespace manifest."""
    return {
        "apiVersion": "v1",
        "kind": "Namespace",
        "metadata": {
            "name": name,
            "labels": {**_COMMON_LABELS},
        },
    }


# ---------------------------------------------------------------------------
# ServiceAccount
# ---------------------------------------------------------------------------


def build_service_account(namespace: str = "citadel-system") -> dict[str, Any]:
    """Return a v1/ServiceAccount manifest."""
    return {
        "apiVersion": "v1",
        "kind": "ServiceAccount",
        "metadata": {
            "name": "citadel-operator",
            "namespace": namespace,
            "labels": {**_COMMON_LABELS},
        },
    }


# ---------------------------------------------------------------------------
# ClusterRole — combined permissions for both operators + kopf
# ---------------------------------------------------------------------------

_CLUSTER_ROLE_RULES: list[dict[str, Any]] = [
    # --- citadel.dev CRDs (both operators) ---
    {
        "apiGroups": ["citadel.dev"],
        "resources": [
            "nodeterminationpolicies",
            "nodeterminationpolicies/status",
            "namespaceinfrastructures",
            "namespaceinfrastructures/status",
        ],
        "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"],
    },
    # --- Cilium network policies (namespace infra operator) ---
    {
        "apiGroups": ["cilium.io"],
        "resources": ["ciliumnetworkpolicies"],
        "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"],
    },
    # --- Node operations (lifecycle operator) ---
    {
        "apiGroups": [""],
        "resources": ["nodes"],
        "verbs": ["get", "list", "watch", "patch", "update"],
    },
    {
        "apiGroups": [""],
        "resources": ["nodes/status"],
        "verbs": ["get"],
    },
    # --- Pod operations (drain + eviction) ---
    {
        "apiGroups": [""],
        "resources": ["pods"],
        "verbs": ["get", "list", "watch", "delete"],
    },
    {
        "apiGroups": [""],
        "resources": ["pods/eviction"],
        "verbs": ["create"],
    },
    # --- Core resources for namespace infra operator ---
    {
        "apiGroups": [""],
        "resources": ["namespaces", "resourcequotas", "limitranges"],
        "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"],
    },
    # --- Events (audit trail) ---
    {
        "apiGroups": [""],
        "resources": ["events"],
        "verbs": ["create", "patch"],
    },
    # --- PDB awareness (drain feasibility) ---
    {
        "apiGroups": ["policy"],
        "resources": ["poddisruptionbudgets"],
        "verbs": ["get", "list", "watch"],
    },
    # --- Leases (kopf leader election) ---
    {
        "apiGroups": ["coordination.k8s.io"],
        "resources": ["leases"],
        "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"],
    },
    # --- CRD discovery (kopf needs this) ---
    {
        "apiGroups": ["apiextensions.k8s.io"],
        "resources": ["customresourcedefinitions"],
        "verbs": ["get", "list", "watch"],
    },
]


def build_cluster_role() -> dict[str, Any]:
    """Return a rbac.authorization.k8s.io/v1/ClusterRole manifest."""
    return {
        "apiVersion": "rbac.authorization.k8s.io/v1",
        "kind": "ClusterRole",
        "metadata": {
            "name": "citadel-operator",
            "labels": {**_COMMON_LABELS},
        },
        "rules": _CLUSTER_ROLE_RULES,
    }


# ---------------------------------------------------------------------------
# ClusterRoleBinding
# ---------------------------------------------------------------------------


def build_cluster_role_binding(namespace: str = "citadel-system") -> dict[str, Any]:
    """Return a rbac.authorization.k8s.io/v1/ClusterRoleBinding manifest."""
    return {
        "apiVersion": "rbac.authorization.k8s.io/v1",
        "kind": "ClusterRoleBinding",
        "metadata": {
            "name": "citadel-operator",
            "labels": {**_COMMON_LABELS},
        },
        "roleRef": {
            "apiGroup": "rbac.authorization.k8s.io",
            "kind": "ClusterRole",
            "name": "citadel-operator",
        },
        "subjects": [
            {
                "kind": "ServiceAccount",
                "name": "citadel-operator",
                "namespace": namespace,
            },
        ],
    }


# ---------------------------------------------------------------------------
# Deployment
# ---------------------------------------------------------------------------

_KOPF_CMD = [
    "kopf",
    "run",
    "--standalone",
    "--liveness=http://0.0.0.0:8081/healthz",
    "-m",
    "citadel_operator.k3s.namespace_infra_operator",
    "-m",
    "citadel_operator.k3s.lifecycle_operator",
]


def build_deployment(
    namespace: str = "citadel-system",
    image: str = "citadel-operator:latest",
) -> dict[str, Any]:
    """Return an apps/v1/Deployment manifest for the kopf operator."""
    return {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "metadata": {
            "name": "citadel-operator",
            "namespace": namespace,
            "labels": {**_COMMON_LABELS},
        },
        "spec": {
            "replicas": 1,
            "selector": {
                "matchLabels": {
                    "app.kubernetes.io/name": "citadel-operator",
                },
            },
            "template": {
                "metadata": {
                    "labels": {**_COMMON_LABELS},
                },
                "spec": {
                    "serviceAccountName": "citadel-operator",
                    "securityContext": {
                        "runAsNonRoot": True,
                        "runAsUser": 1000,
                        "runAsGroup": 1000,
                    },
                    "containers": [
                        {
                            "name": "citadel-operator",
                            "image": image,
                            "imagePullPolicy": "IfNotPresent",
                            "command": _KOPF_CMD,
                            "ports": [
                                {
                                    "containerPort": 8081,
                                    "name": "healthz",
                                    "protocol": "TCP",
                                },
                            ],
                            "livenessProbe": {
                                "httpGet": {
                                    "path": "/healthz",
                                    "port": 8081,
                                },
                                "initialDelaySeconds": 10,
                                "periodSeconds": 30,
                            },
                            "resources": {
                                "requests": {
                                    "cpu": "100m",
                                    "memory": "128Mi",
                                },
                                "limits": {
                                    "cpu": "500m",
                                    "memory": "256Mi",
                                },
                            },
                        },
                    ],
                },
            },
        },
    }


# ---------------------------------------------------------------------------
# CRD loading from bundled YAML
# ---------------------------------------------------------------------------


def _load_yaml_resource(filename: str) -> list[dict[str, Any]]:
    """Load one or more YAML documents from a bundled CRD file.

    Tries ``importlib.resources`` first (wheel installs), then falls back to
    the project-root ``crds/`` directory (editable installs).
    """
    # Try importlib.resources (works in wheel installs with force-include)
    try:
        ref = importlib.resources.files("citadel_operator") / "crds" / filename
        text = ref.read_text(encoding="utf-8")
    except (FileNotFoundError, TypeError):
        # Editable install: walk up from this file to find crds/ at project root
        # Layout: src/citadel_operator/cli/_manifests.py -> ../../../../crds/
        project_root = Path(__file__).resolve().parent.parent.parent.parent
        crd_path = project_root / "crds" / filename
        text = crd_path.read_text(encoding="utf-8")
    docs = [doc for doc in yaml.safe_load_all(text) if doc is not None]
    return docs


def load_crds() -> list[dict[str, Any]]:
    """Return CRD definitions: NodeTerminationPolicy + NamespaceInfrastructure."""
    crds: list[dict[str, Any]] = []
    for filename in ("nodeterminationpolicy.yaml", "namespaceinfrastructure.yaml"):
        for doc in _load_yaml_resource(filename):
            if doc.get("kind") == "CustomResourceDefinition":
                crds.append(doc)
    return crds


def load_default_policies() -> list[dict[str, Any]]:
    """Return the built-in NodeTerminationPolicy instances from default-policies.yaml."""
    policies: list[dict[str, Any]] = []
    for doc in _load_yaml_resource("default-policies.yaml"):
        if doc.get("kind") == "NodeTerminationPolicy":
            # Ensure the managed-by label for CLI tracking
            labels = doc.setdefault("metadata", {}).setdefault("labels", {})
            labels.setdefault("app.kubernetes.io/managed-by", "citadel-cli")
            policies.append(doc)
    return policies


# ---------------------------------------------------------------------------
# Network policy loading from bundled YAML
# ---------------------------------------------------------------------------


def _load_yaml_from_policies(filename: str) -> list[dict[str, Any]]:
    """Load YAML documents from a bundled policies/ file."""
    try:
        ref = importlib.resources.files("citadel_operator") / "policies" / filename
        text = ref.read_text(encoding="utf-8")
    except (FileNotFoundError, TypeError):
        project_root = Path(__file__).resolve().parent.parent.parent.parent
        text = (project_root / "policies" / filename).read_text(encoding="utf-8")
    return [doc for doc in yaml.safe_load_all(text) if doc is not None]


def _list_policy_filenames() -> list[str]:
    """Return sorted policy filenames from the bundled policies/ directory."""
    # Try importlib.resources first (wheel installs)
    try:
        ref = importlib.resources.files("citadel_operator") / "policies"
        return sorted(
            child.name for child in ref.iterdir()
            if child.name.endswith(".yaml")
        )
    except (FileNotFoundError, TypeError, AttributeError):
        pass
    # Fallback: editable install
    project_root = Path(__file__).resolve().parent.parent.parent.parent
    policies_dir = project_root / "policies"
    if not policies_dir.is_dir():
        return []
    return sorted(f.name for f in policies_dir.glob("*.yaml"))


_CILIUM_KINDS = {"CiliumClusterwideNetworkPolicy", "CiliumNetworkPolicy"}


def load_network_policies() -> list[dict[str, Any]]:
    """Return all bundled Cilium network policies, sorted by filename."""
    docs: list[dict[str, Any]] = []
    for name in _list_policy_filenames():
        for doc in _load_yaml_from_policies(name):
            if doc.get("kind") in _CILIUM_KINDS:
                docs.append(doc)
    return docs


# ---------------------------------------------------------------------------
# Convenience: all manifests as a list (for `citadel manifests` output)
# ---------------------------------------------------------------------------


def all_manifests(
    namespace: str = "citadel-system",
    image: str = "citadel-operator:latest",
) -> list[dict[str, Any]]:
    """Return every resource that ``citadel install`` would create, in order."""
    return [
        build_namespace(namespace),
        *load_crds(),
        build_service_account(namespace),
        build_cluster_role(),
        build_cluster_role_binding(namespace),
        build_deployment(namespace, image),
    ]
