"""Tests for citadel_operator.cli._manifests — pure manifest generation."""

import pytest

from citadel_operator.cli._manifests import (
    all_manifests,
    build_cluster_role,
    build_cluster_role_binding,
    build_deployment,
    build_namespace,
    build_service_account,
    load_crds,
    load_default_policies,
)


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


class TestBuildNamespace:
    def test_shape(self):
        ns = build_namespace()
        assert ns["apiVersion"] == "v1"
        assert ns["kind"] == "Namespace"
        assert ns["metadata"]["name"] == "citadel-system"
        assert ns["metadata"]["labels"]["app.kubernetes.io/part-of"] == "citadel"
        assert ns["metadata"]["labels"]["app.kubernetes.io/managed-by"] == "citadel-cli"

    def test_configurable_name(self):
        ns = build_namespace("custom-ns")
        assert ns["metadata"]["name"] == "custom-ns"


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


class TestBuildServiceAccount:
    def test_shape(self):
        sa = build_service_account()
        assert sa["apiVersion"] == "v1"
        assert sa["kind"] == "ServiceAccount"
        assert sa["metadata"]["name"] == "citadel-operator"
        assert sa["metadata"]["namespace"] == "citadel-system"
        assert sa["metadata"]["labels"]["app.kubernetes.io/part-of"] == "citadel"

    def test_configurable_namespace(self):
        sa = build_service_account("test-ns")
        assert sa["metadata"]["namespace"] == "test-ns"


# ---------------------------------------------------------------------------
# ClusterRole
# ---------------------------------------------------------------------------


class TestBuildClusterRole:
    def test_shape(self):
        cr = build_cluster_role()
        assert cr["apiVersion"] == "rbac.authorization.k8s.io/v1"
        assert cr["kind"] == "ClusterRole"
        assert cr["metadata"]["name"] == "citadel-operator"

    def test_covers_citadel_dev_crds(self):
        cr = build_cluster_role()
        citadel_rules = [r for r in cr["rules"] if "citadel.dev" in r["apiGroups"]]
        assert len(citadel_rules) >= 1
        resources = citadel_rules[0]["resources"]
        assert "nodeterminationpolicies" in resources
        assert "nodeterminationpolicies/status" in resources
        assert "namespaceinfrastructures" in resources
        assert "namespaceinfrastructures/status" in resources

    def test_covers_cilium(self):
        cr = build_cluster_role()
        cilium_rules = [r for r in cr["rules"] if "cilium.io" in r["apiGroups"]]
        assert len(cilium_rules) == 1
        assert "ciliumnetworkpolicies" in cilium_rules[0]["resources"]

    def test_covers_nodes(self):
        cr = build_cluster_role()
        node_rules = [
            r for r in cr["rules"] if "" in r["apiGroups"] and "nodes" in r["resources"]
        ]
        assert len(node_rules) >= 1
        assert "patch" in node_rules[0]["verbs"]

    def test_covers_pods_eviction(self):
        cr = build_cluster_role()
        eviction_rules = [
            r for r in cr["rules"] if "" in r["apiGroups"] and "pods/eviction" in r["resources"]
        ]
        assert len(eviction_rules) == 1
        assert "create" in eviction_rules[0]["verbs"]

    def test_covers_namespace_infra_resources(self):
        cr = build_cluster_role()
        core_rules = [r for r in cr["rules"] if "" in r["apiGroups"]]
        all_resources = set()
        for r in core_rules:
            all_resources.update(r["resources"])
        assert "namespaces" in all_resources
        assert "resourcequotas" in all_resources
        assert "limitranges" in all_resources

    def test_covers_leases(self):
        cr = build_cluster_role()
        lease_rules = [
            r for r in cr["rules"] if "coordination.k8s.io" in r["apiGroups"]
        ]
        assert len(lease_rules) == 1
        assert "leases" in lease_rules[0]["resources"]

    def test_covers_crd_discovery(self):
        cr = build_cluster_role()
        crd_rules = [
            r for r in cr["rules"] if "apiextensions.k8s.io" in r["apiGroups"]
        ]
        assert len(crd_rules) == 1
        assert "customresourcedefinitions" in crd_rules[0]["resources"]

    def test_covers_pdbs(self):
        cr = build_cluster_role()
        pdb_rules = [r for r in cr["rules"] if "policy" in r["apiGroups"]]
        assert len(pdb_rules) == 1
        assert "poddisruptionbudgets" in pdb_rules[0]["resources"]


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


class TestBuildClusterRoleBinding:
    def test_shape(self):
        crb = build_cluster_role_binding()
        assert crb["apiVersion"] == "rbac.authorization.k8s.io/v1"
        assert crb["kind"] == "ClusterRoleBinding"
        assert crb["roleRef"]["name"] == "citadel-operator"
        assert crb["subjects"][0]["name"] == "citadel-operator"
        assert crb["subjects"][0]["namespace"] == "citadel-system"

    def test_references_match(self):
        crb = build_cluster_role_binding("custom-ns")
        assert crb["roleRef"]["kind"] == "ClusterRole"
        assert crb["roleRef"]["name"] == "citadel-operator"
        assert crb["subjects"][0]["namespace"] == "custom-ns"


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


class TestBuildDeployment:
    def test_shape(self):
        dep = build_deployment()
        assert dep["apiVersion"] == "apps/v1"
        assert dep["kind"] == "Deployment"
        assert dep["metadata"]["name"] == "citadel-operator"
        assert dep["metadata"]["namespace"] == "citadel-system"

    def test_image_configurable(self):
        dep = build_deployment(image="my-registry/citadel:v2")
        container = dep["spec"]["template"]["spec"]["containers"][0]
        assert container["image"] == "my-registry/citadel:v2"

    def test_namespace_configurable(self):
        dep = build_deployment(namespace="test-ns")
        assert dep["metadata"]["namespace"] == "test-ns"

    def test_liveness_probe(self):
        dep = build_deployment()
        container = dep["spec"]["template"]["spec"]["containers"][0]
        probe = container["livenessProbe"]
        assert probe["httpGet"]["port"] == 8081
        assert probe["httpGet"]["path"] == "/healthz"

    def test_resources(self):
        dep = build_deployment()
        container = dep["spec"]["template"]["spec"]["containers"][0]
        res = container["resources"]
        assert res["requests"]["cpu"] == "100m"
        assert res["requests"]["memory"] == "128Mi"
        assert res["limits"]["cpu"] == "500m"
        assert res["limits"]["memory"] == "256Mi"

    def test_labels(self):
        dep = build_deployment()
        labels = dep["metadata"]["labels"]
        assert labels["app.kubernetes.io/managed-by"] == "citadel-cli"
        assert labels["app.kubernetes.io/name"] == "citadel-operator"
        assert labels["app.kubernetes.io/part-of"] == "citadel"

    def test_security_context(self):
        dep = build_deployment()
        sc = dep["spec"]["template"]["spec"]["securityContext"]
        assert sc["runAsNonRoot"] is True
        assert sc["runAsUser"] == 1000

    def test_command_runs_both_operators(self):
        dep = build_deployment()
        cmd = dep["spec"]["template"]["spec"]["containers"][0]["command"]
        assert "citadel_operator.k3s.namespace_infra_operator" in cmd
        assert "citadel_operator.k3s.lifecycle_operator" in cmd
        assert "--standalone" in cmd

    def test_service_account(self):
        dep = build_deployment()
        assert dep["spec"]["template"]["spec"]["serviceAccountName"] == "citadel-operator"


# ---------------------------------------------------------------------------
# CRD loading
# ---------------------------------------------------------------------------


class TestLoadCRDs:
    def test_returns_two(self):
        crds = load_crds()
        assert len(crds) == 2

    def test_valid_schema(self):
        for crd in load_crds():
            assert crd["apiVersion"] == "apiextensions.k8s.io/v1"
            assert crd["kind"] == "CustomResourceDefinition"
            assert "metadata" in crd
            assert "spec" in crd

    def test_crd_names(self):
        names = {crd["metadata"]["name"] for crd in load_crds()}
        assert "nodeterminationpolicies.citadel.dev" in names
        assert "namespaceinfrastructures.citadel.dev" in names


# ---------------------------------------------------------------------------
# Default policies loading
# ---------------------------------------------------------------------------


class TestLoadDefaultPolicies:
    def test_count(self):
        policies = load_default_policies()
        # 6 NodeTerminationPolicy documents (last YAML doc is a comment block, not a resource)
        assert len(policies) == 6

    def test_labels(self):
        for policy in load_default_policies():
            labels = policy["metadata"]["labels"]
            assert labels["app.kubernetes.io/part-of"] == "citadel"

    def test_all_are_node_termination_policies(self):
        for policy in load_default_policies():
            assert policy["kind"] == "NodeTerminationPolicy"
            assert policy["apiVersion"] == "citadel.dev/v1alpha1"


# ---------------------------------------------------------------------------
# all_manifests convenience
# ---------------------------------------------------------------------------


class TestAllManifests:
    def test_namespace_configurable_across_all(self):
        manifests = all_manifests(namespace="test-ns", image="test:v1")
        ns_manifest = manifests[0]
        assert ns_manifest["kind"] == "Namespace"
        assert ns_manifest["metadata"]["name"] == "test-ns"

        # Find deployment
        dep = [m for m in manifests if m["kind"] == "Deployment"][0]
        assert dep["metadata"]["namespace"] == "test-ns"
        assert dep["spec"]["template"]["spec"]["containers"][0]["image"] == "test:v1"

        # Find ServiceAccount
        sa = [m for m in manifests if m["kind"] == "ServiceAccount"][0]
        assert sa["metadata"]["namespace"] == "test-ns"

        # Find ClusterRoleBinding
        crb = [m for m in manifests if m["kind"] == "ClusterRoleBinding"][0]
        assert crb["subjects"][0]["namespace"] == "test-ns"
