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

from __future__ import annotations

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.exceptions import AppAlreadyExistsError, InvalidAppNameError
from kuberoku.factory import KuberokuFactory
from kuberoku.sdk.apps import AppsService


class TestAppsCreateSDK:
    """SDK-level tests for AppsService.create()."""

    def test_create_happy_path(self, apps_service: AppsService) -> None:
        app = apps_service.create("myapi")
        assert app.name == "myapi"
        assert app.release_version == 0
        assert app.process_types == {}
        assert app.maintenance is False

    def test_create_sets_created_by(self, apps_service: AppsService) -> None:
        app = apps_service.create("myapi")
        assert "@" in app.created_by  # user@host format

    def test_create_returns_frozen_dataclass(self, apps_service: AppsService) -> None:
        app = apps_service.create("myapi")
        with pytest.raises(AttributeError):
            app.name = "other"  # type: ignore[misc]

    def test_create_already_exists(self, apps_service: AppsService) -> None:
        apps_service.create("myapi")
        with pytest.raises(AppAlreadyExistsError, match="myapi"):
            apps_service.create("myapi")

    def test_create_invalid_name_empty(self, apps_service: AppsService) -> None:
        with pytest.raises(InvalidAppNameError, match="empty"):
            apps_service.create("")

    def test_create_invalid_name_uppercase(self, apps_service: AppsService) -> None:
        with pytest.raises(InvalidAppNameError):
            apps_service.create("MyApp")

    def test_create_invalid_name_too_long(self, apps_service: AppsService) -> None:
        with pytest.raises(InvalidAppNameError, match="at most 30"):
            apps_service.create("a" * 31)

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

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

    def test_create_invalid_name_special_chars(self, apps_service: AppsService) -> None:
        with pytest.raises(InvalidAppNameError):
            apps_service.create("my_app")

    def test_create_valid_boundary_names(self, apps_service: AppsService) -> None:
        # Single char
        app1 = apps_service.create("a")
        assert app1.name == "a"
        # Two chars
        app2 = apps_service.create("ab")
        assert app2.name == "ab"
        # Max length (30 chars)
        app3 = apps_service.create("a" * 30)
        assert app3.name == "a" * 30

    def test_create_on_step_callback(self, apps_service: AppsService) -> None:
        steps: list[str] = []
        apps_service.create("myapi", on_step=steps.append)
        assert len(steps) >= 3
        assert "manifest" in steps[0].lower()

    def test_create_idempotent_namespace(self, factory: KuberokuFactory) -> None:
        """Creating two apps doesn't fail on namespace creation."""
        factory.apps.create("app1")
        factory.apps.create("app2")

    def test_create_stores_configmaps(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        apps_service.create("myapi")
        # Verify the 3 ConfigMaps were created
        k8s = factory.k8s
        ns = factory.namespace
        p = factory.prefix
        k8s.get_configmap(ns, f"{p}-app-myapi")
        k8s.get_configmap(ns, f"{p}-env-myapi")
        k8s.get_configmap(ns, f"{p}-formation-myapi")

    def test_create_creates_deny_network_policy(
        self, apps_service: AppsService, factory: KuberokuFactory
    ) -> None:
        """apps:create creates a default-deny NetworkPolicy for app isolation."""
        apps_service.create("myapi")
        k8s = factory.k8s
        ns = factory.namespace
        np = k8s.get_network_policy(ns, f"{factory.prefix}-netpol-deny-myapi")
        assert np["metadata"]["labels"][f"{factory.domain}/app"] == "myapi"
        assert np["metadata"]["labels"][f"{factory.domain}/resource-type"] == "network-policy-deny"
        # Deny-all: empty ingress list (real K8s omits empty lists)
        assert np["spec"].get("ingress", []) == []
        # Selects pods with app label
        assert np["spec"]["podSelector"]["matchLabels"][f"{factory.domain}/app"] == "myapi"

    def test_create_on_step_includes_network_policy(self, apps_service: AppsService) -> None:
        steps: list[str] = []
        apps_service.create("myapi", on_step=steps.append)
        policy_steps = [s for s in steps if "network" in s.lower() or "isolation" in s.lower()]
        assert len(policy_steps) >= 1


class TestAppsCreateCLI:
    """CLI-level tests for apps:create."""

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

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

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

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

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

    def test_create_cli_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(cli, ["apps:create", "--help"])
        assert result.exit_code == 0
        assert "Create a new app" in result.output
