"""Tests for auto-domain integration during deploy.

Verifies that DeployService.deploy() calls factory.domains.auto_domain()
when a web process is deployed and KUBEROKU_BASE_DOMAIN is set,
and that the resulting Ingress has the correct hostname, backend, and TLS.
"""

from __future__ import annotations

import os
import unittest.mock as mock

from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import resources
from tests.backends.fake import FakeK8sClient


def _make_k8s_with_infra() -> FakeK8sClient:
    """Create a FakeK8sClient with nginx IngressClass + cert-manager."""
    client = FakeK8sClient()

    # Install nginx IngressClass
    client.create_ingress_class(
        {
            "apiVersion": "networking.k8s.io/v1",
            "kind": "IngressClass",
            "metadata": {"name": "nginx"},
            "spec": {"controller": "k8s.io/ingress-nginx"},
        }
    )

    # Install cert-manager (Deployment in cert-manager namespace)
    client.create_deployment(
        "cert-manager",
        {
            "apiVersion": "apps/v1",
            "kind": "Deployment",
            "metadata": {"name": "cert-manager"},
            "spec": {
                "replicas": 1,
                "selector": {"matchLabels": {"app": "cert-manager"}},
                "template": {
                    "metadata": {"labels": {"app": "cert-manager"}},
                    "spec": {"containers": [{"name": "cm", "image": "cert-manager:v1.14"}]},
                },
            },
        },
    )

    return client


class TestDeployAutoDomain:
    """Auto-domain creation during deploy with a web process type."""

    def test_deploy_creates_ingress_when_base_domain_set(self) -> None:
        """Deploying a web process with KUBEROKU_BASE_DOMAIN creates an Ingress."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            release = factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
            assert release.version == 1

            # Verify Ingress was created
            ingress_name = resources.safe_name(factory.prefix, "ingress", "myapp")
            ingress = k8s.get_ingress(factory.namespace, ingress_name)
            rules = ingress["spec"]["rules"]
            assert len(rules) == 1
            assert rules[0]["host"] == "myapp.apps.example.com"

    def test_deploy_ingress_has_correct_backend(self) -> None:
        """Ingress rule points to the correct backend service name and port."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            factory.deploy.deploy("myapp", image="myapp:v1", wait=False)

            ingress_name = resources.safe_name(factory.prefix, "ingress", "myapp")
            ingress = k8s.get_ingress(factory.namespace, ingress_name)
            rule = ingress["spec"]["rules"][0]

            # Backend service name must match the web process Service
            expected_svc_name = resources.safe_name(factory.prefix, "myapp", "web")
            backend = rule["http"]["paths"][0]["backend"]["service"]
            assert backend["name"] == expected_svc_name
            assert backend["port"]["number"] == 8080

    def test_deploy_ingress_has_tls_with_cert_manager(self) -> None:
        """Ingress has TLS entries when cert-manager is available."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            factory.deploy.deploy("myapp", image="myapp:v1", wait=False)

            ingress_name = resources.safe_name(factory.prefix, "ingress", "myapp")
            ingress = k8s.get_ingress(factory.namespace, ingress_name)
            tls = ingress["spec"].get("tls", [])
            assert len(tls) == 1
            assert "myapp.apps.example.com" in tls[0]["hosts"]
            assert tls[0]["secretName"] == "tls-myapp-apps-example-com"

    def test_deploy_on_step_reports_app_url(self) -> None:
        """The on_step callback reports the auto-domain URL."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            steps: list[str] = []
            factory.deploy.deploy("myapp", image="myapp:v1", wait=False, on_step=steps.append)

            # Should have a step reporting the URL
            url_steps = [s for s in steps if "https://myapp.apps.example.com" in s]
            assert len(url_steps) == 1

    def test_deploy_no_base_domain_no_ingress(self) -> None:
        """Without KUBEROKU_BASE_DOMAIN, no Ingress is created."""
        k8s = _make_k8s_with_infra()
        # No KUBEROKU_BASE_DOMAIN set
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
        factory.apps.create("myapp")

        factory.deploy.deploy("myapp", image="myapp:v1", wait=False)

        # No Ingress should exist
        ingresses = k8s.list_ingresses(factory.namespace)
        assert len(ingresses) == 0

    def test_deploy_worker_only_no_auto_domain(self) -> None:
        """Deploying only a worker (not web) does not trigger auto-domain."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            factory.deploy.deploy("myapp", image="myapp:v1", process_type="worker", wait=False)

            # No Ingress should exist (worker, not web)
            ingresses = k8s.list_ingresses(factory.namespace)
            assert len(ingresses) == 0

    def test_deploy_auto_domain_idempotent_on_redeploy(self) -> None:
        """Redeploying web does not create duplicate Ingress rules."""
        k8s = _make_k8s_with_infra()
        with mock.patch.dict(os.environ, {"KUBEROKU_BASE_DOMAIN": "apps.example.com"}):
            factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
            factory.apps.create("myapp")

            factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
            factory.deploy.deploy("myapp", image="myapp:v2", wait=False)

            ingress_name = resources.safe_name(factory.prefix, "ingress", "myapp")
            ingress = k8s.get_ingress(factory.namespace, ingress_name)
            rules = ingress["spec"]["rules"]
            # Should still have just 1 rule, not duplicated
            assert len(rules) == 1
            assert rules[0]["host"] == "myapp.apps.example.com"
