"""CLI integration tests for domains commands.

These tests verify BOTH CLI output AND K8s state after each operation.
"""

from __future__ import annotations

from typing import Any

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import resources
from kuberoku.sdk.ingress import detect_ingress_controller
from tests.backends.fake import FakeNotFoundError


def _get_ingress(k8s: Any, factory: KuberokuFactory, app: str) -> dict[str, Any]:
    """Get the Ingress resource for an app."""
    ingress_name = resources.safe_name(factory.prefix, "ingress", app)
    return k8s.get_ingress(factory.namespace, ingress_name)


def _get_ingress_hostnames(ingress: dict[str, Any]) -> list[str]:
    """Extract hostnames from Ingress rules."""
    return [r["host"] for r in ingress.get("spec", {}).get("rules", [])]


def _get_ingress_tls_hosts(ingress: dict[str, Any]) -> set[str]:
    """Extract TLS hostnames from Ingress."""
    hosts: set[str] = set()
    for entry in ingress.get("spec", {}).get("tls", []):
        for h in entry.get("hosts", []):
            hosts.add(h)
    return hosts


def _get_backend_service(rule: dict[str, Any]) -> tuple[str, int]:
    """Extract (service_name, port) from an Ingress rule."""
    backend = rule["http"]["paths"][0]["backend"]["service"]
    return backend["name"], backend["port"]["number"]


class TestDomainsCliList:
    def test_list_empty(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        factory.apps.create("myapp")

        result = cli_runner.invoke(cli, ["domains", "--app", "myapp"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "No domains" in result.output

    def test_list_with_domains(
        self, cli_runner: CliRunner, factory: KuberokuFactory, app_with_web: str
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")

        result = cli_runner.invoke(
            cli, ["domains", "--app", app_with_web], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "api.example.com" in result.output

    def test_list_shows_multiple_domains(
        self, cli_runner: CliRunner, factory: KuberokuFactory, app_with_web: str
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        factory.domains.add(app_with_web, "admin.example.com")

        result = cli_runner.invoke(
            cli, ["domains", "--app", app_with_web], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "api.example.com" in result.output
        assert "admin.example.com" in result.output


class TestDomainsCliAdd:
    def test_add_domain(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        result = cli_runner.invoke(
            cli,
            ["domains", "add", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "api.example.com" in result.output

        # Verify K8s state: Ingress was created
        ingress = _get_ingress(k8s, factory, app_with_web)
        assert "api.example.com" in _get_ingress_hostnames(ingress)
        # Verify backend service name + port
        rule = ingress["spec"]["rules"][0]
        svc_name, svc_port = _get_backend_service(rule)
        expected_svc = resources.safe_name(factory.prefix, app_with_web, "web")
        assert svc_name == expected_svc
        assert svc_port == 8080
        # Verify TLS is enabled (cert-manager present)
        assert "api.example.com" in _get_ingress_tls_hosts(ingress)
        # Verify ingressClassName matches detected controller
        ic = detect_ingress_controller(factory.k8s, factory.namespace)
        assert ic is not None
        assert ingress["spec"].get("ingressClassName") == ic.class_name

    def test_add_with_no_tls(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        result = cli_runner.invoke(
            cli,
            ["domains", "add", "api.example.com", "--app", app_with_web, "--no-tls"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "none" in result.output

        # Verify K8s state: Ingress created with no TLS
        ingress = _get_ingress(k8s, factory, app_with_web)
        assert "api.example.com" in _get_ingress_hostnames(ingress)
        assert _get_ingress_tls_hosts(ingress) == set()  # No TLS

    def test_add_creates_external_network_policy(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        result = cli_runner.invoke(
            cli,
            ["domains", "add", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

        # Verify NetworkPolicy was created with "domains" owner
        np_name = resources.safe_name(
            factory.prefix,
            "netpol-external",
            f"{app_with_web}-web",
        )
        np = k8s.get_network_policy(factory.namespace, np_name)
        annots = np["metadata"].get("annotations", {})
        assert "domains" in annots.get(f"{factory.domain}/exposed-by", "")

    def test_add_colon_syntax(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        result = cli_runner.invoke(
            cli,
            ["domains:add", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

        # Verify K8s state even for colon syntax
        ingress = _get_ingress(k8s, factory, app_with_web)
        assert "api.example.com" in _get_ingress_hostnames(ingress)

    def test_add_second_domain_appends_rule(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        cli_runner.invoke(
            cli,
            ["domains", "add", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        cli_runner.invoke(
            cli,
            ["domains", "add", "admin.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )

        # Verify both rules in same Ingress
        ingress = _get_ingress(k8s, factory, app_with_web)
        hostnames = _get_ingress_hostnames(ingress)
        assert "api.example.com" in hostnames
        assert "admin.example.com" in hostnames
        # Should be one Ingress with two rules
        assert len(ingress["spec"]["rules"]) == 2


class TestDomainsCliRemove:
    def test_remove_domain(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")

        result = cli_runner.invoke(
            cli,
            ["domains", "remove", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Removed" in result.output

        # Verify K8s state: Ingress deleted (was the only rule)
        ingress_name = resources.safe_name(factory.prefix, "ingress", app_with_web)
        with pytest.raises(FakeNotFoundError):
            k8s.get_ingress(factory.namespace, ingress_name)

    def test_remove_one_of_two_keeps_other(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        factory.domains.add(app_with_web, "admin.example.com")

        result = cli_runner.invoke(
            cli,
            ["domains", "remove", "api.example.com", "--app", app_with_web],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

        # Verify K8s state: Ingress still exists with only admin domain
        ingress = _get_ingress(k8s, factory, app_with_web)
        hostnames = _get_ingress_hostnames(ingress)
        assert "api.example.com" not in hostnames
        assert "admin.example.com" in hostnames

    def test_remove_nonexistent(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        factory.apps.create("myapp")

        result = cli_runner.invoke(
            cli,
            ["domains", "remove", "nonexistent.com", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0


class TestDomainsCliClear:
    def test_clear_with_confirm(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")

        result = cli_runner.invoke(
            cli,
            ["domains", "clear", "--app", app_with_web, "--confirm"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Cleared" in result.output

        # Verify K8s state: Ingress fully deleted
        ingress_name = resources.safe_name(factory.prefix, "ingress", app_with_web)
        with pytest.raises(FakeNotFoundError):
            k8s.get_ingress(factory.namespace, ingress_name)

    def test_clear_removes_multiple_domains(
        self,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        k8s: Any,
        app_with_web: str,
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        factory.domains.add(app_with_web, "admin.example.com")

        result = cli_runner.invoke(
            cli,
            ["domains", "clear", "--app", app_with_web, "--confirm"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

        # Verify K8s state: Ingress fully deleted despite having 2 rules
        ingress_name = resources.safe_name(factory.prefix, "ingress", app_with_web)
        with pytest.raises(FakeNotFoundError):
            k8s.get_ingress(factory.namespace, ingress_name)

        # Verify domains list is empty
        assert factory.domains.list(app_with_web) == []

    def test_clear_without_confirm_prompts(
        self, cli_runner: CliRunner, factory: KuberokuFactory
    ) -> None:
        factory.apps.create("myapp")

        result = cli_runner.invoke(
            cli,
            ["domains", "clear", "--app", "myapp"],
            obj={"factory": factory},
            input="y\n",
        )
        assert result.exit_code == 0
