"""Tests for domains:remove."""

from __future__ import annotations

from typing import Any

import pytest

from kuberoku.exceptions import (
    AppNotFoundError,
    AutoDomainRemoveError,
    DomainNotFoundError,
)
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import resources
from tests.backends.fake import FakeNotFoundError


class TestDomainsRemove:
    def test_remove_single_domain(
        self, factory: KuberokuFactory, k8s: Any, app_with_web: str
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        factory.domains.remove(app_with_web, "api.example.com")

        # Ingress should be deleted (last rule removed)
        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(
        self, 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")
        factory.domains.remove(app_with_web, "api.example.com")

        ingress_name = resources.safe_name(factory.prefix, "ingress", app_with_web)
        ingress = k8s.get_ingress(factory.namespace, ingress_name)
        rules = ingress["spec"]["rules"]
        assert len(rules) == 1
        assert rules[0]["host"] == "admin.example.com"

    def test_remove_nonexistent_raises(self, factory: KuberokuFactory, app_with_web: str) -> None:
        with pytest.raises(DomainNotFoundError):
            factory.domains.remove(app_with_web, "nonexistent.com")

    def test_remove_auto_generated_raises(
        self, factory: KuberokuFactory, app_with_web: str
    ) -> None:
        factory.domains.add(app_with_web, "auto.example.com", auto_generated=True)
        with pytest.raises(AutoDomainRemoveError):
            factory.domains.remove(app_with_web, "auto.example.com")

    def test_remove_nonexistent_app_raises(self, factory: KuberokuFactory) -> None:
        with pytest.raises(AppNotFoundError):
            factory.domains.remove("nonexistent", "api.example.com")

    def test_remove_tls_entry_also_removed(
        self, 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")
        factory.domains.remove(app_with_web, "api.example.com")

        ingress_name = resources.safe_name(factory.prefix, "ingress", app_with_web)
        ingress = k8s.get_ingress(factory.namespace, ingress_name)
        tls = ingress["spec"].get("tls", [])
        tls_hosts = [h for t in tls for h in t.get("hosts", [])]
        assert "api.example.com" not in tls_hosts
        assert "admin.example.com" in tls_hosts

    def test_remove_no_ingress_raises(self, factory: KuberokuFactory, app_with_web: str) -> None:
        # No domains added = no Ingress
        with pytest.raises(DomainNotFoundError):
            factory.domains.remove(app_with_web, "api.example.com")

    def test_remove_on_step_callback(self, factory: KuberokuFactory, app_with_web: str) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        steps: list[str] = []
        factory.domains.remove(app_with_web, "api.example.com", on_step=steps.append)
        assert len(steps) > 0

    def test_remove_hostname_not_in_rules_raises(
        self, factory: KuberokuFactory, app_with_web: str
    ) -> None:
        factory.domains.add(app_with_web, "api.example.com")
        with pytest.raises(DomainNotFoundError):
            factory.domains.remove(app_with_web, "other.example.com")


class TestDomainsRemoveExternalPolicyCleanup:
    """Test that removing a domain cleans up the external-allow NetworkPolicy."""

    def test_remove_domain_deletes_external_policy(
        self, factory: KuberokuFactory, k8s: Any, app_with_web: str
    ) -> None:
        """domains:add creates policy with 'domains'; domains:remove deletes it."""
        factory.domains.add(app_with_web, "api.example.com")

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

        # Remove the domain
        factory.domains.remove(app_with_web, "api.example.com")

        # External policy should be deleted (no other owners)
        with pytest.raises(FakeNotFoundError):
            k8s.get_network_policy(factory.namespace, np_name)

    def test_remove_one_of_two_domains_keeps_policy(
        self, factory: KuberokuFactory, k8s: Any, app_with_web: str
    ) -> None:
        """Removing one domain when another remains still cleans up policy owner."""
        factory.domains.add(app_with_web, "api.example.com")
        factory.domains.add(app_with_web, "admin.example.com")

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

        # Remove one domain — cleanup_external_policy_owner removes "domains"
        # but the Ingress still has a rule, so the policy owner is cleaned
        factory.domains.remove(app_with_web, "api.example.com")

        # Since the remove() method always removes "domains" from the policy
        # and "domains" is the only owner, the policy gets deleted
        with pytest.raises(FakeNotFoundError):
            k8s.get_network_policy(factory.namespace, np_name)
