"""Tests for the namespace infrastructure resource builder.

Tests pure builder functions — dict in, dict out. No API calls.
"""

import pytest

from citadel_operator.k3s.namespace_infra_builder import (
    LABEL_MANAGED_BY,
    MANAGED_BY_VALUE,
    build_default_deny_policy,
    build_limit_range,
    build_peer_policies,
    build_resource_quota,
    build_tenant_allow_policy,
)
from citadel_operator.k3s.namespace_infra_models import (
    LimitRangeConfig,
    NetworkPolicyConfig,
    ResourceQuotaConfig,
)


class TestBuildDefaultDenyPolicy:
    def test_basic_structure(self):
        config = NetworkPolicyConfig()
        policy = build_default_deny_policy("test-ns", config)

        assert policy["apiVersion"] == "cilium.io/v2"
        assert policy["kind"] == "CiliumNetworkPolicy"
        assert policy["metadata"]["name"] == "test-ns-default-deny"
        assert policy["metadata"]["namespace"] == "test-ns"

    def test_managed_by_label(self):
        config = NetworkPolicyConfig()
        policy = build_default_deny_policy("test-ns", config)

        assert policy["metadata"]["labels"][LABEL_MANAGED_BY] == MANAGED_BY_VALUE

    def test_dns_egress_allowed(self):
        config = NetworkPolicyConfig()
        policy = build_default_deny_policy("test-ns", config)

        egress = policy["spec"]["egress"]
        dns_rule = egress[0]
        assert dns_rule["toEndpoints"][0]["matchLabels"]["k8s-app"] == "kube-dns"

        ports = dns_rule["toPorts"][0]["ports"]
        port_values = {(p["port"], p["protocol"]) for p in ports}
        assert ("53", "UDP") in port_values
        assert ("53", "TCP") in port_values

    def test_kube_api_egress_allowed(self):
        config = NetworkPolicyConfig()
        policy = build_default_deny_policy("test-ns", config)

        egress = policy["spec"]["egress"]
        api_rule = egress[1]
        assert "kube-apiserver" in api_rule["toEntities"]

    def test_ingress_is_empty(self):
        config = NetworkPolicyConfig()
        policy = build_default_deny_policy("test-ns", config)

        assert policy["spec"]["ingress"] == []


class TestBuildTenantAllowPolicy:
    def test_basic_structure(self):
        config = NetworkPolicyConfig()
        policy = build_tenant_allow_policy("test-ns", config)

        assert policy["metadata"]["name"] == "test-ns-tenant-allow"
        assert policy["metadata"]["namespace"] == "test-ns"

    def test_kube_system_ingress(self):
        config = NetworkPolicyConfig(allow_kube_system=True, allow_monitoring=False)
        policy = build_tenant_allow_policy("test-ns", config)

        ingress = policy["spec"]["ingress"]
        kube_sys_rules = [
            r for r in ingress
            if any(
                ep.get("matchLabels", {}).get("k8s:io.kubernetes.pod.namespace") == "kube-system"
                for ep in r.get("fromEndpoints", [])
            )
        ]
        assert len(kube_sys_rules) == 1

    def test_monitoring_ingress(self):
        config = NetworkPolicyConfig(allow_monitoring=True, allow_kube_system=False)
        policy = build_tenant_allow_policy("test-ns", config)

        ingress = policy["spec"]["ingress"]
        monitoring_rules = [
            r for r in ingress
            if any(
                ep.get("matchLabels", {}).get("k8s:io.kubernetes.pod.namespace") == "monitoring"
                for ep in r.get("fromEndpoints", [])
            )
        ]
        assert len(monitoring_rules) == 1

    def test_intra_namespace_communication(self):
        config = NetworkPolicyConfig(allow_kube_system=False, allow_monitoring=False)
        policy = build_tenant_allow_policy("test-ns", config)

        ingress = policy["spec"]["ingress"]
        intra_rules = [
            r for r in ingress
            if any(
                ep.get("matchLabels", {}).get("k8s:io.kubernetes.pod.namespace") == "test-ns"
                for ep in r.get("fromEndpoints", [])
            )
        ]
        assert len(intra_rules) == 1

        egress = policy["spec"]["egress"]
        intra_egress = [
            r for r in egress
            if any(
                ep.get("matchLabels", {}).get("k8s:io.kubernetes.pod.namespace") == "test-ns"
                for ep in r.get("toEndpoints", [])
            )
        ]
        assert len(intra_egress) == 1

    def test_no_kube_system_when_disabled(self):
        config = NetworkPolicyConfig(allow_kube_system=False, allow_monitoring=False)
        policy = build_tenant_allow_policy("test-ns", config)

        ingress = policy["spec"]["ingress"]
        kube_sys_rules = [
            r for r in ingress
            if any(
                ep.get("matchLabels", {}).get("k8s:io.kubernetes.pod.namespace") == "kube-system"
                for ep in r.get("fromEndpoints", [])
            )
        ]
        assert len(kube_sys_rules) == 0


class TestBuildPeerPolicies:
    def test_empty_peers(self):
        config = NetworkPolicyConfig(allowed_peers=())
        policies = build_peer_policies("test-ns", config)
        assert len(policies) == 0

    def test_single_peer(self):
        config = NetworkPolicyConfig(allowed_peers=("peer-ns",))
        policies = build_peer_policies("test-ns", config)

        assert len(policies) == 1
        assert policies[0]["metadata"]["name"] == "test-ns-peer-peer-ns"
        assert policies[0]["metadata"]["namespace"] == "test-ns"

    def test_multiple_peers(self):
        config = NetworkPolicyConfig(allowed_peers=("ns-a", "ns-b", "ns-c"))
        policies = build_peer_policies("test-ns", config)

        assert len(policies) == 3
        names = {p["metadata"]["name"] for p in policies}
        assert names == {
            "test-ns-peer-ns-a",
            "test-ns-peer-ns-b",
            "test-ns-peer-ns-c",
        }

    def test_bidirectional(self):
        config = NetworkPolicyConfig(allowed_peers=("peer-ns",))
        policies = build_peer_policies("test-ns", config)

        policy = policies[0]
        assert "ingress" in policy["spec"]
        assert "egress" in policy["spec"]

    def test_managed_by_label(self):
        config = NetworkPolicyConfig(allowed_peers=("peer-ns",))
        policies = build_peer_policies("test-ns", config)

        assert policies[0]["metadata"]["labels"][LABEL_MANAGED_BY] == MANAGED_BY_VALUE


class TestBuildResourceQuota:
    def test_all_fields(self):
        config = ResourceQuotaConfig(
            cpu="4", memory="8Gi", pods="100", storage="100Gi", pvcs="10"
        )
        rq = build_resource_quota("test-ns", config)

        assert rq is not None
        assert rq["metadata"]["name"] == "test-ns-quota"
        assert rq["spec"]["hard"]["limits.cpu"] == "4"
        assert rq["spec"]["hard"]["limits.memory"] == "8Gi"
        assert rq["spec"]["hard"]["pods"] == "100"
        assert rq["spec"]["hard"]["requests.storage"] == "100Gi"
        assert rq["spec"]["hard"]["persistentvolumeclaims"] == "10"

    def test_partial_fields(self):
        config = ResourceQuotaConfig(cpu="2")
        rq = build_resource_quota("test-ns", config)

        assert rq is not None
        assert rq["spec"]["hard"] == {"limits.cpu": "2"}

    def test_empty_returns_none(self):
        config = ResourceQuotaConfig()
        rq = build_resource_quota("test-ns", config)

        assert rq is None

    def test_custom_fields(self):
        config = ResourceQuotaConfig(custom=(("count/deployments.apps", "10"),))
        rq = build_resource_quota("test-ns", config)

        assert rq is not None
        assert rq["spec"]["hard"]["count/deployments.apps"] == "10"

    def test_managed_by_label(self):
        config = ResourceQuotaConfig(cpu="2")
        rq = build_resource_quota("test-ns", config)

        assert rq["metadata"]["labels"][LABEL_MANAGED_BY] == MANAGED_BY_VALUE


class TestBuildLimitRange:
    def test_default_values(self):
        config = LimitRangeConfig()
        lr = build_limit_range("test-ns", config)

        assert lr is not None
        assert lr["metadata"]["name"] == "test-ns-limits"

        limits = lr["spec"]["limits"][0]
        assert limits["type"] == "Container"
        assert limits["default"]["cpu"] == "500m"
        assert limits["default"]["memory"] == "512Mi"
        assert limits["defaultRequest"]["cpu"] == "100m"
        assert limits["defaultRequest"]["memory"] == "128Mi"
        assert limits["max"]["cpu"] == "4"
        assert limits["max"]["memory"] == "8Gi"

    def test_custom_values(self):
        config = LimitRangeConfig(
            default_cpu="1", default_memory="1Gi",
            default_request_cpu="250m", default_request_memory="256Mi",
            max_cpu="8", max_memory="16Gi",
        )
        lr = build_limit_range("test-ns", config)

        limits = lr["spec"]["limits"][0]
        assert limits["default"]["cpu"] == "1"
        assert limits["default"]["memory"] == "1Gi"
        assert limits["max"]["cpu"] == "8"

    def test_managed_by_label(self):
        config = LimitRangeConfig()
        lr = build_limit_range("test-ns", config)

        assert lr["metadata"]["labels"][LABEL_MANAGED_BY] == MANAGED_BY_VALUE
