"""Tests for exceptions.py — every exception class must have a test."""

import pytest

from kuberoku.exceptions import (
    AddonAlreadyExistsError,
    AddonNotFoundError,
    AddonTypeNotSupportedError,
    AmbiguousNameError,
    AmbiguousProcessTypeError,
    AppAlreadyExistsError,
    AppNotFoundError,
    AutoDomainRemoveError,
    CertManagerNotFoundError,
    ClusterNotFoundError,
    ClusterUnreachableError,
    ConfigLimitError,
    ConfigVarNotFoundError,
    DeployError,
    DomainAlreadyExistsError,
    DomainNotFoundError,
    GatewayPortExhaustedError,
    ImageNotFoundError,
    IngressControllerNotFoundError,
    InvalidAppNameError,
    InvalidConfigKeyError,
    KuberokuError,
    NameNotFoundError,
    NoPortsError,
    NotExposedError,
    PermissionDeniedError,
    PluginError,
    PortAlreadyExistsError,
    PortNotFoundError,
    ReleaseNotFoundError,
    RolloutTimeoutError,
)


class TestKuberokuErrorBase:
    def test_base_is_exception(self) -> None:
        assert issubclass(KuberokuError, Exception)

    def test_instantiate_with_message(self) -> None:
        err = KuberokuError("something went wrong")
        assert str(err) == "something went wrong"


class TestAppErrors:
    def test_app_not_found_message(self) -> None:
        err = AppNotFoundError("myapi")
        assert "myapi" in str(err)
        assert err.name == "myapi"
        assert isinstance(err, KuberokuError)

    def test_app_already_exists_message(self) -> None:
        err = AppAlreadyExistsError("myapi")
        assert "myapi" in str(err)
        assert err.name == "myapi"
        assert isinstance(err, KuberokuError)


class TestValidationErrors:
    def test_invalid_app_name(self) -> None:
        err = InvalidAppNameError("BAD", "must be lowercase")
        assert "BAD" in str(err)
        assert "lowercase" in str(err)
        assert err.name == "BAD"
        assert err.reason == "must be lowercase"
        assert isinstance(err, KuberokuError)


class TestDeployErrors:
    def test_deploy_error_hierarchy(self) -> None:
        assert issubclass(DeployError, KuberokuError)

    def test_rollout_timeout_is_deploy_error(self) -> None:
        err = RolloutTimeoutError("timed out")
        assert isinstance(err, DeployError)
        assert isinstance(err, KuberokuError)

    def test_image_not_found_is_deploy_error(self) -> None:
        err = ImageNotFoundError("image pull failed")
        assert isinstance(err, DeployError)


class TestConfigErrors:
    def test_config_var_not_found(self) -> None:
        err = ConfigVarNotFoundError("DATABASE_URL", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.key == "DATABASE_URL"
        assert err.app == "myapp"
        assert "DATABASE_URL" in str(err)

    def test_config_limit_error(self) -> None:
        err = ConfigLimitError("too many vars")
        assert isinstance(err, KuberokuError)

    def test_invalid_config_key_error(self) -> None:
        err = InvalidConfigKeyError("bad-key", "must be uppercase")
        assert isinstance(err, KuberokuError)
        assert err.key == "bad-key"
        assert err.reason == "must be uppercase"
        assert "bad-key" in str(err)


class TestReleaseErrors:
    def test_release_not_found(self) -> None:
        err = ReleaseNotFoundError("v99 not found")
        assert isinstance(err, KuberokuError)


class TestClusterErrors:
    def test_cluster_not_found(self) -> None:
        err = ClusterNotFoundError("production not configured")
        assert isinstance(err, KuberokuError)

    def test_cluster_unreachable(self) -> None:
        err = ClusterUnreachableError("connection refused")
        assert isinstance(err, KuberokuError)


class TestPermissionErrors:
    def test_permission_denied_basic(self) -> None:
        err = PermissionDeniedError("create", "deployments")
        assert "create" in str(err)
        assert "deployments" in str(err)
        assert err.verb == "create"
        assert err.resource == "deployments"

    def test_permission_denied_with_suggestion(self) -> None:
        err = PermissionDeniedError("create", "secrets", "Run: kuberoku doctor --fix")
        assert "kuberoku doctor --fix" in str(err)


class TestAddonErrors:
    def test_addon_not_found(self) -> None:
        err = AddonNotFoundError("postgres", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.instance_name == "postgres"
        assert err.app == "myapp"

    def test_addon_already_exists(self) -> None:
        err = AddonAlreadyExistsError("postgres", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.instance_name == "postgres"
        assert err.app == "myapp"
        assert "--as" in str(err)

    def test_addon_type_not_supported(self) -> None:
        err = AddonTypeNotSupportedError("mongodb not supported")
        assert isinstance(err, KuberokuError)


class TestNetworkingErrors:
    def test_ambiguous_name(self) -> None:
        err = AmbiguousNameError("web", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.name == "web"
        assert err.app == "myapp"
        assert "--process" in str(err)
        assert "--addon" in str(err)

    def test_name_not_found(self) -> None:
        err = NameNotFoundError("foobar", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.name == "foobar"
        assert err.app == "myapp"
        assert "foobar" in str(err)

    def test_not_exposed(self) -> None:
        err = NotExposedError("web", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.name == "web"
        assert err.app == "myapp"
        assert "not currently exposed" in str(err)


class TestPortErrors:
    def test_no_ports_error(self) -> None:
        err = NoPortsError("web", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.name == "web"
        assert err.app == "myapp"
        assert "no ports" in str(err)

    def test_port_already_exists(self) -> None:
        err = PortAlreadyExistsError(8080, "TCP", "web", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.port == 8080
        assert err.protocol == "TCP"
        assert err.process_type == "web"
        assert err.app == "myapp"
        assert "8080/tcp" in str(err)

    def test_port_not_found(self) -> None:
        err = PortNotFoundError(9090, "UDP", "worker", "myapp")
        assert isinstance(err, KuberokuError)
        assert err.port == 9090
        assert err.protocol == "UDP"
        assert err.process_type == "worker"
        assert "9090/udp" in str(err)

    def test_ambiguous_process_type(self) -> None:
        err = AmbiguousProcessTypeError("myapp", ("web", "worker"))
        assert isinstance(err, KuberokuError)
        assert err.app == "myapp"
        assert err.types == ("web", "worker")
        assert "--type" in str(err)
        assert "web" in str(err)
        assert "worker" in str(err)


class TestDomainErrors:
    def test_domain_already_exists(self) -> None:
        err = DomainAlreadyExistsError("myapp.com", "myapi")
        assert isinstance(err, KuberokuError)
        assert err.hostname == "myapp.com"
        assert err.app == "myapi"
        assert "myapp.com" in str(err)
        assert "myapi" in str(err)


class TestPluginErrors:
    def test_plugin_error(self) -> None:
        err = PluginError("failed to load kuberoku-postgres")
        assert isinstance(err, KuberokuError)


class TestAllExceptionsAreCatchable:
    """Verify the error boundary pattern: catch KuberokuError at the top level."""

    ALL_EXCEPTIONS = [
        AppNotFoundError("x"),
        AppAlreadyExistsError("x"),
        InvalidAppNameError("x", "bad"),
        DeployError("x"),
        RolloutTimeoutError("x"),
        ImageNotFoundError("x"),
        ConfigVarNotFoundError("x", "app"),
        ConfigLimitError("x"),
        InvalidConfigKeyError("x", "bad"),
        ReleaseNotFoundError("x"),
        ClusterNotFoundError("x"),
        ClusterUnreachableError("x"),
        PermissionDeniedError("v", "r"),
        AddonNotFoundError("x", "app"),
        AddonAlreadyExistsError("x", "app"),
        AddonTypeNotSupportedError("x"),
        AmbiguousNameError("x", "app"),
        NameNotFoundError("x", "app"),
        NotExposedError("x", "app"),
        NoPortsError("x", "app"),
        PortAlreadyExistsError(8080, "TCP", "web", "app"),
        PortNotFoundError(8080, "TCP", "web", "app"),
        AmbiguousProcessTypeError("app", ("web", "worker")),
        DomainAlreadyExistsError("x", "app"),
        DomainNotFoundError("x", "app"),
        AutoDomainRemoveError("x"),
        IngressControllerNotFoundError(),
        CertManagerNotFoundError(),
        GatewayPortExhaustedError(),
        PluginError("x"),
    ]

    @pytest.mark.parametrize("exc", ALL_EXCEPTIONS, ids=lambda e: type(e).__name__)
    def test_all_caught_by_base(self, exc: KuberokuError) -> None:
        with pytest.raises(KuberokuError):
            raise exc
