"""Tests for apps:destroy — SDK + CLI."""

from __future__ import annotations

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.exceptions import AppNotFoundError
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import resources
from kuberoku.sdk.apps import AppsService
from tests.backends.fake import FakeK8sClient, FakeNotFoundError


class TestAppsDestroySDK:
    """SDK-level tests for AppsService.destroy()."""

    def test_destroy_happy_path(self, apps_service: AppsService) -> None:
        apps_service.create("myapi")
        apps_service.destroy("myapi")
        assert apps_service.list() == []

    def test_destroy_not_found(self, apps_service: AppsService) -> None:
        with pytest.raises(AppNotFoundError, match="myapi"):
            apps_service.destroy("myapi")

    def test_destroy_on_step_callback(self, apps_service: AppsService) -> None:
        apps_service.create("myapi")
        steps: list[str] = []
        apps_service.destroy("myapi", on_step=steps.append)
        assert len(steps) >= 1

    def test_destroy_removes_all_configmaps(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        apps_service.create("myapi")
        apps_service.destroy("myapi")
        k8s = factory.k8s
        ns = factory.namespace
        # All CMs with app label should be gone
        cms = k8s.list_configmaps(
            ns,
            labels={
                f"{factory.domain}/app": "myapi",
            },
        )
        assert cms == []

    def test_destroy_one_app_leaves_others(self, apps_service: AppsService) -> None:
        apps_service.create("app1")
        apps_service.create("app2")
        apps_service.destroy("app1")
        remaining = [a.name for a in apps_service.list()]
        assert remaining == ["app2"]

    def test_destroy_cleans_up_network_policies(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """Destroy also deletes the deny NetworkPolicy."""
        apps_service.create("myapi")
        k8s = factory.k8s
        ns = factory.namespace
        # Verify policy exists before destroy
        nps = k8s.list_network_policies(ns, labels={f"{factory.domain}/app": "myapi"})
        assert len(nps) == 1
        apps_service.destroy("myapi")
        # Verify policy is gone after destroy
        nps = k8s.list_network_policies(ns, labels={f"{factory.domain}/app": "myapi"})
        assert nps == []

    def test_destroy_cleans_up_secrets(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """Destroy deletes the Secret created by config:set --secret."""
        apps_service.create("myapi")
        factory.config.set("myapi", {"TOKEN": "secret"}, secret=True)
        # Verify secret exists
        k8s = factory.k8s
        ns = factory.namespace
        p = factory.prefix
        k8s.get_secret(ns, f"{p}-secret-myapi")  # should not raise
        apps_service.destroy("myapi")
        # Verify secret is gone
        with pytest.raises((FakeNotFoundError, Exception)):
            k8s.get_secret(ns, f"{p}-secret-myapi")

    def test_destroy_cleans_up_deployments_and_services(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """Destroy also deletes deployments and services with matching labels."""
        apps_service.create("myapi")
        # Use SDK deploy (creates Deployment + Service with correct labels)
        factory.deploy.deploy("myapi", image="nginx:1.27", wait=False)
        k8s = factory.k8s
        ns = factory.namespace
        apps_service.destroy("myapi")
        # Verify everything is gone
        assert k8s.list_deployments(ns, labels={f"{factory.domain}/app": "myapi"}) == []
        assert k8s.list_services(ns, labels={f"{factory.domain}/app": "myapi"}) == []


class TestAppsDestroyIngressCleanup:
    """Test that apps:destroy cleans up Ingress resources created by domains."""

    def test_destroy_removes_ingress(self) -> None:
        """Create app, deploy, add domain, then destroy. Ingress should be gone."""
        k8s = FakeK8sClient()

        # Install nginx IngressClass and cert-manager
        k8s.create_ingress_class(
            {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "IngressClass",
                "metadata": {"name": "nginx"},
                "spec": {"controller": "k8s.io/ingress-nginx"},
            }
        )
        k8s.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"}]},
                    },
                },
            },
        )

        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")

        # Create app and deploy
        factory.apps.create("myapi")
        factory.deploy.deploy("myapi", image="myapi:v1", wait=False)

        # Add a domain
        factory.domains.add("myapi", "api.example.com")

        # Verify Ingress exists
        ingress_name = resources.safe_name(factory.prefix, "ingress", "myapi")
        ingress = k8s.get_ingress(factory.namespace, ingress_name)
        assert ingress["spec"]["rules"][0]["host"] == "api.example.com"

        # Destroy the app
        factory.apps.destroy("myapi")

        # Verify Ingress is gone
        with pytest.raises(FakeNotFoundError):
            k8s.get_ingress(factory.namespace, ingress_name)

    def test_destroy_no_ingress_is_safe(self) -> None:
        """Destroying an app with no Ingress should not raise."""
        k8s = FakeK8sClient()
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")

        factory.apps.create("myapi")
        factory.deploy.deploy("myapi", image="myapi:v1", wait=False)
        # No domain added; destroy should still succeed
        factory.apps.destroy("myapi")

        # Verify the app is gone
        assert factory.apps.list() == []


class TestAppsDestroyCLI:
    """CLI-level tests for apps:destroy."""

    def test_destroy_cli_with_confirm(
        self, cli_runner: CliRunner, factory: KuberokuFactory
    ) -> None:
        cli_runner.invoke(cli, ["apps:create", "myapi"], obj={"factory": factory})
        result = cli_runner.invoke(
            cli, ["apps:destroy", "myapi", "--confirm", "myapi"], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "Destroyed app myapi" in result.output

    def test_destroy_cli_wrong_confirm(
        self, cli_runner: CliRunner, factory: KuberokuFactory
    ) -> None:
        cli_runner.invoke(cli, ["apps:create", "myapi"], obj={"factory": factory})
        result = cli_runner.invoke(
            cli,
            ["apps:destroy", "myapi", "--confirm", "wrong"],
            input="n\n",
            obj={"factory": factory},
        )
        assert "Aborted" in result.output

    def test_destroy_cli_not_found(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        result = cli_runner.invoke(
            cli, ["apps:destroy", "nope", "--confirm", "nope"], obj={"factory": factory}
        )
        assert result.exit_code != 0
