"""Tests for config:unset — SDK + CLI."""

from __future__ import annotations

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.exceptions import AppNotFoundError, ConfigVarNotFoundError
from kuberoku.factory import KuberokuFactory
from kuberoku.sdk.config import ConfigService


class TestConfigUnsetSDK:
    """SDK-level tests for ConfigService.unset()."""

    def test_unset_single_var(self, config_service: ConfigService, app_name: str) -> None:
        config_service.set(app_name, {"KEY": "value"})
        result = config_service.unset(app_name, ["KEY"])
        assert "KEY" not in result

    def test_unset_multiple_vars(self, config_service: ConfigService, app_name: str) -> None:
        config_service.set(app_name, {"A": "1", "B": "2", "C": "3"})
        result = config_service.unset(app_name, ["A", "B"])
        assert "A" not in result
        assert "B" not in result
        assert result["C"] == "3"

    def test_unset_from_secret(self, config_service: ConfigService, app_name: str) -> None:
        config_service.set(app_name, {"TOKEN": "secret"}, secret=True)
        result = config_service.unset(app_name, ["TOKEN"])
        assert "TOKEN" not in result

    def test_unset_key_not_found(self, config_service: ConfigService, app_name: str) -> None:
        with pytest.raises(ConfigVarNotFoundError, match="MISSING"):
            config_service.unset(app_name, ["MISSING"])

    def test_unset_app_not_found(self, config_service: ConfigService) -> None:
        with pytest.raises(AppNotFoundError, match="nonexistent"):
            config_service.unset("nonexistent", ["KEY"])

    def test_unset_on_step_callback(self, config_service: ConfigService, app_name: str) -> None:
        config_service.set(app_name, {"KEY": "val"})
        steps: list[str] = []
        config_service.unset(app_name, ["KEY"], on_step=steps.append)
        assert len(steps) >= 1

    def test_unset_mixed_sources(self, config_service: ConfigService, app_name: str) -> None:
        """Unset vars from both ConfigMap and Secret in one call."""
        config_service.set(app_name, {"CM_KEY": "1"})
        config_service.set(app_name, {"SECRET_KEY": "2"}, secret=True)
        result = config_service.unset(app_name, ["CM_KEY", "SECRET_KEY"])
        assert "CM_KEY" not in result
        assert "SECRET_KEY" not in result

    def test_unset_key_in_both_sources(self, config_service: ConfigService, app_name: str) -> None:
        """If a key exists in both ConfigMap and Secret, unset removes from both."""
        config_service.set(app_name, {"KEY": "cm_value"})
        config_service.set(app_name, {"KEY": "secret_value"}, secret=True)
        result = config_service.unset(app_name, ["KEY"])
        assert "KEY" not in result


class TestConfigUnsetRetry:
    """Tests for optimistic concurrency retry on 409 Conflict."""

    @pytest.fixture(autouse=True)
    def _skip_real_backend(self, request: pytest.FixtureRequest) -> None:
        if request.config.getoption("--backend") == "real":
            pytest.skip("inject_conflict is only available on FakeK8sClient")

    def test_unset_configmap_retries_on_conflict(
        self, config_service: ConfigService, app_name: str, factory: KuberokuFactory
    ) -> None:
        """unset() retries on 409 when unsetting from ConfigMap."""
        config_service.set(app_name, {"KEY": "value"})
        k8s = factory.k8s
        cm_name = f"{factory.prefix}-env-{app_name}"
        k8s.inject_conflict("ConfigMap", factory.namespace, cm_name, count=1)
        result = config_service.unset(app_name, ["KEY"])
        assert "KEY" not in result

    def test_unset_secret_retries_on_conflict(
        self, config_service: ConfigService, app_name: str, factory: KuberokuFactory
    ) -> None:
        """unset() retries on 409 when unsetting from Secret."""
        config_service.set(app_name, {"TOKEN": "secret"}, secret=True)
        k8s = factory.k8s
        secret_name = f"{factory.prefix}-secret-{app_name}"
        k8s.inject_conflict("Secret", factory.namespace, secret_name, count=1)
        result = config_service.unset(app_name, ["TOKEN"])
        assert "TOKEN" not in result


class TestConfigUnsetCLI:
    """CLI-level tests for config:unset."""

    def test_unset_cli(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        factory.apps.create("myapp")
        factory.config.set("myapp", {"KEY": "value"})
        result = cli_runner.invoke(
            cli, ["config:unset", "KEY", "--app", "myapp"], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "KEY" in result.output

    def test_unset_cli_multiple(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        factory.apps.create("myapp")
        factory.config.set("myapp", {"A": "1", "B": "2"})
        result = cli_runner.invoke(
            cli,
            ["config:unset", "A", "B", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

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

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

    def test_unset_cli_space_syntax(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        """'config unset KEY' works same as 'config:unset KEY'."""
        factory.apps.create("myapp")
        factory.config.set("myapp", {"KEY": "value"})
        result = cli_runner.invoke(
            cli, ["config", "unset", "KEY", "--app", "myapp"], obj={"factory": factory}
        )
        assert result.exit_code == 0
        assert "KEY" in result.output
