"""Tests for k8s/resources.py — resource builders and safe_name."""

from kuberoku.k8s.resources import (
    build_addon_allow_policy,
    build_app_deny_policy,
    build_app_manifest,
    build_deployment,
    build_env_configmap,
    build_external_allow_policy,
    build_formation_configmap,
    build_ingress,
    build_ingress_rule,
    build_job,
    build_process_allow_policy,
    build_release_configmap,
    build_secret,
    build_service,
    build_statefulset,
    build_tls_entry,
    safe_name,
)

PREFIX = "kuberoku"
DOMAIN = "app.kuberoku.com"


class TestSafeName:
    def test_short_name_unchanged(self) -> None:
        assert safe_name("kuberoku", "myapi", "web") == "kuberoku-myapi-web"

    def test_no_suffix(self) -> None:
        assert safe_name("kuberoku", "myapi") == "kuberoku-myapi"

    def test_within_63_chars(self) -> None:
        name = safe_name("kuberoku", "myapi", "web")
        assert len(name) <= 63

    def test_truncates_long_app_name(self) -> None:
        long_app = "a" * 50
        name = safe_name("kuberoku", long_app, "web")
        assert len(name) <= 63
        assert name.endswith("-web")

    def test_truncated_name_has_hash(self) -> None:
        long_app = "a" * 55  # long enough to trigger truncation
        name = safe_name("kuberoku", long_app, "web")
        assert len(name) <= 63
        # Hash is 4 hex chars before the suffix
        parts = name.split("-")
        # The hash is the second-to-last part (before "web")
        assert len(parts[-2]) == 4

    def test_same_app_same_hash(self) -> None:
        """Truncation hash is stable (same app → same hash)."""
        long_app = "my-very-long-application-name-that-exceeds"
        n1 = safe_name("kuberoku", long_app, "web")
        n2 = safe_name("kuberoku", long_app, "web")
        assert n1 == n2

    def test_different_apps_different_hash(self) -> None:
        n1 = safe_name("kuberoku", "a" * 50, "web")
        n2 = safe_name("kuberoku", "b" * 50, "web")
        assert n1 != n2

    def test_no_truncation_when_fits(self) -> None:
        name = safe_name("kuberoku", "short", "web")
        assert name == "kuberoku-short-web"
        # No hash when not needed
        assert len(name.split("-")) == 3

    def test_custom_max_length(self) -> None:
        name = safe_name("kuberoku", "myapi", "web", max_length=20)
        assert len(name) <= 20

    def test_truncated_no_suffix_has_hash(self) -> None:
        """Truncation without suffix still includes a hash."""
        long_app = "a" * 55
        name = safe_name("kuberoku", long_app)
        assert len(name) <= 63
        # Should have hash at the end
        parts = name.split("-")
        assert len(parts[-1]) == 4  # 4-char hash

    def test_prefix_types(self) -> None:
        """Various resource name patterns from the spec."""
        assert safe_name("kuberoku", "app", "myapi") == "kuberoku-app-myapi"
        assert safe_name("kuberoku", "env", "myapi") == "kuberoku-env-myapi"
        assert safe_name("kuberoku", "formation", "myapi") == "kuberoku-formation-myapi"


class TestBuildAppManifest:
    def test_structure(self) -> None:
        result = build_app_manifest(PREFIX, DOMAIN, "myapi", "user@host")
        assert result["kind"] == "ConfigMap"
        assert result["metadata"]["name"] == "kuberoku-app-myapi"
        assert result["data"]["current_release_version"] == "0"

    def test_labels(self) -> None:
        result = build_app_manifest(PREFIX, DOMAIN, "myapi", "user@host")
        lbl = result["metadata"]["labels"]
        assert lbl["app.kuberoku.com/app"] == "myapi"
        assert lbl["app.kuberoku.com/resource-type"] == "app-manifest"

    def test_annotations(self) -> None:
        result = build_app_manifest(PREFIX, DOMAIN, "myapi", "user@host")
        ann = result["metadata"]["annotations"]
        assert "app.kuberoku.com/created-at" in ann
        assert ann["app.kuberoku.com/created-by"] == "user@host"


class TestBuildEnvConfigmap:
    def test_empty_data(self) -> None:
        result = build_env_configmap(PREFIX, DOMAIN, "myapi")
        assert result["data"] == {}
        assert result["metadata"]["name"] == "kuberoku-env-myapi"


class TestBuildFormationConfigmap:
    def test_empty_data(self) -> None:
        result = build_formation_configmap(PREFIX, DOMAIN, "myapi")
        assert result["data"] == {}
        assert result["metadata"]["name"] == "kuberoku-formation-myapi"


class TestBuildSecret:
    def test_opaque_type(self) -> None:
        result = build_secret(PREFIX, DOMAIN, "myapi")
        assert result["type"] == "Opaque"
        assert result["stringData"] == {}


class TestBuildDeployment:
    def test_structure(self) -> None:
        result = build_deployment(PREFIX, DOMAIN, "myapi", "web", "myapi:v1", 2, None)
        assert result["kind"] == "Deployment"
        assert result["spec"]["replicas"] == 2
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert container["image"] == "myapi:v1"
        assert container["name"] == "web"

    def test_command_override(self) -> None:
        result = build_deployment(
            PREFIX, DOMAIN, "myapi", "web", "myapi:v1", 1, "gunicorn app:app"
        )
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert container["command"] == ["/bin/sh", "-c", "gunicorn app:app"]

    def test_no_command_when_none(self) -> None:
        result = build_deployment(PREFIX, DOMAIN, "myapi", "web", "myapi:v1", 1, None)
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert "command" not in container

    def test_env_from_refs(self) -> None:
        result = build_deployment(PREFIX, DOMAIN, "myapi", "web", "myapi:v1", 1, None)
        container = result["spec"]["template"]["spec"]["containers"][0]
        env_from = container["envFrom"]
        assert env_from[0]["configMapRef"]["name"] == "kuberoku-env-myapi"
        assert env_from[1]["secretRef"]["name"] == "kuberoku-secret-myapi"
        assert env_from[1]["secretRef"]["optional"] is True

    def test_ports(self) -> None:
        ports = [{"containerPort": 8080, "protocol": "TCP"}]
        result = build_deployment(PREFIX, DOMAIN, "myapi", "web", "myapi:v1", 1, None, ports=ports)
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert container["ports"] == ports


class TestBuildService:
    def test_structure(self) -> None:
        ports = [{"port": 80, "targetPort": 8080, "protocol": "TCP"}]
        result = build_service(PREFIX, DOMAIN, "myapi", "web", ports)
        assert result["kind"] == "Service"
        assert result["spec"]["type"] == "ClusterIP"
        assert result["spec"]["ports"] == ports


class TestBuildIngressRule:
    def test_structure(self) -> None:
        rule = build_ingress_rule("myapi.example.com", "kuberoku-myapi-web", 80)
        assert rule["host"] == "myapi.example.com"
        path = rule["http"]["paths"][0]
        assert path["path"] == "/"
        assert path["pathType"] == "Prefix"
        backend = path["backend"]["service"]
        assert backend["name"] == "kuberoku-myapi-web"
        assert backend["port"]["number"] == 80


class TestBuildTlsEntry:
    def test_structure(self) -> None:
        entry = build_tls_entry("myapi.example.com", "myapi-tls")
        assert entry["hosts"] == ["myapi.example.com"]
        assert entry["secretName"] == "myapi-tls"


class TestBuildIngress:
    def test_structure(self) -> None:
        rules = [build_ingress_rule("myapi.example.com", "kuberoku-myapi-web", 80)]
        result = build_ingress(PREFIX, DOMAIN, "myapi", rules)
        assert result["kind"] == "Ingress"
        rule = result["spec"]["rules"][0]
        assert rule["host"] == "myapi.example.com"

    def test_with_tls(self) -> None:
        rules = [build_ingress_rule("myapi.example.com", "kuberoku-myapi-web", 80)]
        tls = [build_tls_entry("myapi.example.com", "myapi-tls")]
        result = build_ingress(
            PREFIX,
            DOMAIN,
            "myapi",
            rules,
            tls=tls,
            ingress_class="nginx",
            issuer="letsencrypt-prod",
        )
        assert result["spec"]["ingressClassName"] == "nginx"
        assert result["spec"]["tls"] == tls
        annots = result["metadata"]["annotations"]
        assert annots["cert-manager.io/cluster-issuer"] == "letsencrypt-prod"

    def test_with_auto_hostnames(self) -> None:
        rules = [build_ingress_rule("auto.example.com", "kuberoku-myapi-web", 80)]
        result = build_ingress(
            PREFIX,
            DOMAIN,
            "myapi",
            rules,
            auto_hostnames=["auto.example.com"],
        )
        annots = result["metadata"]["annotations"]
        assert "auto.example.com" in annots[f"{DOMAIN}/auto-domains"]


class TestBuildStatefulset:
    def test_structure(self) -> None:
        result = build_statefulset(
            PREFIX,
            DOMAIN,
            "myapi",
            "postgres",
            "postgres",
            "postgres:16",
            5432,
            volumes={"/var/lib/postgresql/data": "data"},
        )
        assert result["kind"] == "StatefulSet"
        assert result["spec"]["replicas"] == 1
        assert len(result["spec"]["volumeClaimTemplates"]) == 1

    def test_statefulset_with_command(self) -> None:
        result = build_statefulset(
            PREFIX,
            DOMAIN,
            "myapi",
            "postgres",
            "postgres",
            "postgres:16",
            5432,
            command=["/bin/sh", "-c", "start"],
        )
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert container["command"] == ["/bin/sh", "-c", "start"]

    def test_statefulset_with_storage_class(self) -> None:
        result = build_statefulset(
            PREFIX,
            DOMAIN,
            "myapi",
            "postgres",
            "postgres",
            "postgres:16",
            5432,
            volumes={"/data": "data"},
            storage_class="ssd",
        )
        vct = result["spec"]["volumeClaimTemplates"][0]
        assert vct["spec"]["storageClassName"] == "ssd"


class TestBuildReleaseConfigmap:
    def test_structure(self) -> None:
        result = build_release_configmap(
            PREFIX,
            DOMAIN,
            "myapi",
            3,
            {"version": "3", "description": "Deploy myapi:v3"},
        )
        assert result["kind"] == "ConfigMap"
        assert "v3" in result["metadata"]["name"]


class TestBuildJob:
    def test_structure(self) -> None:
        result = build_job(
            PREFIX,
            DOMAIN,
            "myapi",
            "abc123",
            "myapi:v1",
            ["python", "manage.py", "migrate"],
        )
        assert result["kind"] == "Job"
        assert result["spec"]["backoffLimit"] == 0
        container = result["spec"]["template"]["spec"]["containers"][0]
        assert container["command"] == ["python", "manage.py", "migrate"]


class TestBuildAppDenyPolicy:
    def test_structure(self) -> None:
        result = build_app_deny_policy(PREFIX, DOMAIN, "myapi")
        assert result["apiVersion"] == "networking.k8s.io/v1"
        assert result["kind"] == "NetworkPolicy"
        assert result["metadata"]["name"] == "kuberoku-netpol-deny-myapi"
        assert result["spec"]["ingress"] == []
        assert result["spec"]["policyTypes"] == ["Ingress"]

    def test_labels(self) -> None:
        result = build_app_deny_policy(PREFIX, DOMAIN, "myapi")
        lbl = result["metadata"]["labels"]
        assert lbl[f"{DOMAIN}/managed-by"] == PREFIX
        assert lbl[f"{DOMAIN}/app"] == "myapi"
        assert lbl[f"{DOMAIN}/resource-type"] == "network-policy-deny"

    def test_pod_selector(self) -> None:
        result = build_app_deny_policy(PREFIX, DOMAIN, "myapi")
        sel = result["spec"]["podSelector"]["matchLabels"]
        assert sel[f"{DOMAIN}/app"] == "myapi"


class TestBuildProcessAllowPolicy:
    def test_structure(self) -> None:
        ports = [{"port": 8080, "protocol": "TCP"}]
        result = build_process_allow_policy(PREFIX, DOMAIN, "myapi", "web", ports)
        assert result["kind"] == "NetworkPolicy"
        assert result["metadata"]["name"] == "kuberoku-netpol-allow-myapi-web"
        assert result["spec"]["policyTypes"] == ["Ingress"]

    def test_allows_same_app_on_ports(self) -> None:
        ports = [{"port": 8080, "protocol": "TCP"}]
        result = build_process_allow_policy(PREFIX, DOMAIN, "myapi", "web", ports)
        ingress = result["spec"]["ingress"][0]
        assert ingress["ports"] == ports
        from_sel = ingress["from"][0]["podSelector"]["matchLabels"]
        assert from_sel[f"{DOMAIN}/app"] == "myapi"

    def test_labels_include_process_type(self) -> None:
        ports = [{"port": 8080, "protocol": "TCP"}]
        result = build_process_allow_policy(PREFIX, DOMAIN, "myapi", "web", ports)
        lbl = result["metadata"]["labels"]
        assert lbl[f"{DOMAIN}/process-type"] == "web"
        assert lbl[f"{DOMAIN}/resource-type"] == "network-policy-allow"


class TestBuildAddonAllowPolicy:
    def test_structure(self) -> None:
        result = build_addon_allow_policy(PREFIX, DOMAIN, "myapi", "maindb", "postgres", 5432)
        assert result["kind"] == "NetworkPolicy"
        assert "maindb" in result["metadata"]["name"]

    def test_allows_same_app_on_addon_port(self) -> None:
        result = build_addon_allow_policy(PREFIX, DOMAIN, "myapi", "maindb", "postgres", 5432)
        ingress = result["spec"]["ingress"][0]
        assert ingress["ports"] == [{"port": 5432, "protocol": "TCP"}]
        from_sel = ingress["from"][0]["podSelector"]["matchLabels"]
        assert from_sel[f"{DOMAIN}/app"] == "myapi"


class TestBuildExternalAllowPolicy:
    def test_structure(self) -> None:
        ports = [{"port": 8080, "protocol": "TCP"}]
        result = build_external_allow_policy(PREFIX, DOMAIN, "myapi", "web", ports)
        assert result["kind"] == "NetworkPolicy"
        assert "external" in result["metadata"]["name"]

    def test_allows_from_all_sources(self) -> None:
        ports = [{"port": 8080, "protocol": "TCP"}]
        result = build_external_allow_policy(PREFIX, DOMAIN, "myapi", "web", ports)
        ingress = result["spec"]["ingress"][0]
        assert ingress["ports"] == ports
        # No "from" key = allow from everywhere
        assert "from" not in ingress
