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

from __future__ import annotations

import pytest
from click.testing import CliRunner

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


class TestAppsRenameSDK:
    """SDK-level tests for AppsService.rename()."""

    def test_rename_happy_path(self, apps_service: AppsService) -> None:
        apps_service.create("oldname")
        app = apps_service.rename("oldname", "newname")
        assert app.name == "newname"
        # Old name should no longer exist
        with pytest.raises(AppNotFoundError):
            apps_service.info("oldname")

    def test_rename_not_found(self, apps_service: AppsService) -> None:
        with pytest.raises(AppNotFoundError, match="nope"):
            apps_service.rename("nope", "newname")

    def test_rename_target_exists(self, apps_service: AppsService) -> None:
        apps_service.create("app1")
        apps_service.create("app2")
        with pytest.raises(AppAlreadyExistsError, match="app2"):
            apps_service.rename("app1", "app2")

    def test_rename_invalid_new_name(self, apps_service: AppsService) -> None:
        apps_service.create("myapp")
        with pytest.raises(InvalidAppNameError):
            apps_service.rename("myapp", "INVALID")

    def test_rename_preserves_release_version(self, apps_service: AppsService) -> None:
        apps_service.create("myapp")
        app = apps_service.rename("myapp", "newapp")
        assert app.release_version == 0

    def test_rename_on_step_callback(self, apps_service: AppsService) -> None:
        apps_service.create("myapp")
        steps: list[str] = []
        apps_service.rename("myapp", "newapp", on_step=steps.append)
        assert len(steps) >= 1

    def test_rename_appears_in_list(self, apps_service: AppsService) -> None:
        apps_service.create("oldname")
        apps_service.rename("oldname", "newname")
        names = [a.name for a in apps_service.list()]
        assert "newname" in names
        assert "oldname" not in names

    def test_rename_updates_network_policies(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """Rename updates labels on network policies too."""
        apps_service.create("oldapp")
        apps_service.rename("oldapp", "newapp")
        k8s = factory.k8s
        ns = factory.namespace
        # Old app label should have no policies
        old_nps = k8s.list_network_policies(ns, labels={f"{factory.domain}/app": "oldapp"})
        assert old_nps == []
        # New app label should have the deny policy
        new_nps = k8s.list_network_policies(ns, labels={f"{factory.domain}/app": "newapp"})
        assert len(new_nps) == 1

    def test_rename_updates_deployments_and_services(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """Rename updates labels on deployments and services too."""
        apps_service.create("oldapp")
        # Use SDK deploy (creates Deployment + Service with correct labels)
        factory.deploy.deploy("oldapp", image="nginx:1.27", wait=False)
        k8s = factory.k8s
        ns = factory.namespace
        apps_service.rename("oldapp", "newapp")
        # Verify labels were updated
        deps = k8s.list_deployments(ns, labels={f"{factory.domain}/app": "newapp"})
        assert len(deps) == 1
        svcs = k8s.list_services(ns, labels={f"{factory.domain}/app": "newapp"})
        assert len(svcs) == 1

    def test_rename_preserves_config_vars(self, factory: KuberokuFactory) -> None:
        """Config vars (env ConfigMap) must survive a rename."""
        factory.apps.create("oldapp")
        factory.config.set("oldapp", {"GREETING": "hello", "DB_URL": "postgres://localhost"})
        factory.apps.rename("oldapp", "newapp")
        # Config vars should be accessible on the new name
        value, source = factory.config.get("newapp", "GREETING")
        assert value == "hello"
        assert source == "configmap"
        value2, _ = factory.config.get("newapp", "DB_URL")
        assert value2 == "postgres://localhost"
        # Old name should not have config
        with pytest.raises(AppNotFoundError):
            factory.config.get("oldapp", "GREETING")

    def test_rename_preserves_secret_vars(self, factory: KuberokuFactory) -> None:
        """Secret config vars must survive a rename."""
        factory.apps.create("oldapp")
        factory.config.set("oldapp", {"API_KEY": "secret123"}, secret=True)
        factory.apps.rename("oldapp", "newapp")
        value, source = factory.config.get("newapp", "API_KEY")
        assert value == "secret123"
        assert source == "secret"

    def test_rename_retries_deployment_update_on_409(self) -> None:
        """Deployment update during rename retries on 409 Conflict.

        On real K8s, the deployment controller continuously reconciles,
        changing resourceVersion between our list and update. This test
        deterministically proves the retry works using inject_conflict().
        """
        k8s = FakeK8sClient()
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
        factory.apps.create("oldapp")
        factory.deploy.deploy("oldapp", image="nginx:latest", wait=False)

        # Inject a 409 conflict on the deployment update
        dep_name = resources.safe_name(factory.prefix, "oldapp", "web")
        k8s.inject_conflict("Deployment", factory.namespace, dep_name, count=2)

        # Rename should succeed despite 2 consecutive 409s
        app = factory.apps.rename("oldapp", "newapp")
        assert app.name == "newapp"

        # Verify the deployment label was actually updated
        deps = k8s.list_deployments(factory.namespace, labels={f"{factory.domain}/app": "newapp"})
        assert len(deps) == 1

    def test_rename_exhausts_retries_then_succeeds_on_final_attempt(self) -> None:
        """All retry attempts get 409, but the final attempt after the loop succeeds."""
        k8s = FakeK8sClient()
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
        factory.apps.create("oldapp")
        factory.deploy.deploy("oldapp", image="nginx:latest", wait=False)

        dep_name = resources.safe_name(factory.prefix, "oldapp", "web")
        # 3 conflicts = exhausts the retry loop, succeeds on final attempt
        k8s.inject_conflict("Deployment", factory.namespace, dep_name, count=3)

        app = factory.apps.rename("oldapp", "newapp")
        assert app.name == "newapp"


class TestAppsRenameCLI:
    """CLI-level tests for apps:rename."""

    def test_rename_cli(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        factory.apps.create("oldname")
        result = cli_runner.invoke(
            cli, ["apps:rename", "oldname", "newname"], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "Renamed to newname" in result.output

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