"""Tests for service expose/unexpose/connect via gateway service."""

from __future__ import annotations

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.exceptions import NotExposedError
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s.resources import safe_name
from tests.backends.fake import FakeNotFoundError


class TestServiceExposeGateway:
    """Test service expose_on with method=gateway."""

    def test_expose_on_gateway_creates_allocation(self, deployed_factory: KuberokuFactory) -> None:
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert endpoint.method == "gateway"
        assert endpoint.gateway_port is not None
        assert 10000 <= endpoint.gateway_port <= 10999

    def test_expose_on_gateway_creates_lb_service(self, deployed_factory: KuberokuFactory) -> None:
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert endpoint.gateway_port is not None
        gw_svc_name = f"{deployed_factory.prefix}-gw-{endpoint.gateway_port}"
        gw_svc = deployed_factory.k8s.get_service(deployed_factory.namespace, gw_svc_name)
        assert gw_svc["spec"]["type"] == "LoadBalancer"

    def test_expose_on_gateway_lb_service_has_selector(
        self, deployed_factory: KuberokuFactory
    ) -> None:
        """Gateway LB Service should copy the target service's selector."""
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert endpoint.gateway_port is not None
        gw_svc_name = f"{deployed_factory.prefix}-gw-{endpoint.gateway_port}"
        gw_svc = deployed_factory.k8s.get_service(deployed_factory.namespace, gw_svc_name)
        selector = gw_svc["spec"]["selector"]
        assert len(selector) > 0  # Should have copied from target

    def test_expose_on_gateway_is_idempotent(self, deployed_factory: KuberokuFactory) -> None:
        e1 = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        e2 = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert e1.gateway_port == e2.gateway_port

    def test_expose_on_gateway_creates_network_policy(
        self, deployed_factory: KuberokuFactory
    ) -> None:
        deployed_factory.services.expose_on("myapp", "web", method="gateway")
        np_name = safe_name(deployed_factory.prefix, "netpol-external", "myapp-web")
        np = deployed_factory.k8s.get_network_policy(deployed_factory.namespace, np_name)
        assert np is not None

    def test_expose_on_loadbalancer_still_works(self, deployed_factory: KuberokuFactory) -> None:
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="loadbalancer")
        assert endpoint.method == "loadbalancer"
        assert endpoint.gateway_port is None
        svc_name = safe_name(deployed_factory.prefix, "myapp", "web")
        svc = deployed_factory.k8s.get_service(deployed_factory.namespace, svc_name)
        assert svc["spec"]["type"] == "LoadBalancer"


class TestServiceUnexposeGateway:
    """Test service expose_off when exposed via gateway."""

    def test_expose_off_gateway_deallocates(self, deployed_factory: KuberokuFactory) -> None:
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert endpoint.gateway_port is not None

        result = deployed_factory.services.expose_off("myapp", "web")
        assert result.method == "clusterip"

        # Verify allocation is gone
        svc_name = safe_name(deployed_factory.prefix, "myapp", "web")
        alloc = deployed_factory.gateway.get_allocation("myapp", svc_name)
        assert alloc is None

    def test_expose_off_gateway_removes_lb_service(
        self, deployed_factory: KuberokuFactory
    ) -> None:
        endpoint = deployed_factory.services.expose_on("myapp", "web", method="gateway")
        assert endpoint.gateway_port is not None
        gw_svc_name = f"{deployed_factory.prefix}-gw-{endpoint.gateway_port}"

        deployed_factory.services.expose_off("myapp", "web")
        with pytest.raises(FakeNotFoundError):
            deployed_factory.k8s.get_service(deployed_factory.namespace, gw_svc_name)

    def test_expose_off_gateway_removes_network_policy(
        self, deployed_factory: KuberokuFactory
    ) -> None:
        deployed_factory.services.expose_on("myapp", "web", method="gateway")
        deployed_factory.services.expose_off("myapp", "web")
        np_name = safe_name(deployed_factory.prefix, "netpol-external", "myapp-web")
        with pytest.raises(FakeNotFoundError):
            deployed_factory.k8s.get_network_policy(deployed_factory.namespace, np_name)

    def test_expose_off_raises_if_not_exposed(self, deployed_factory: KuberokuFactory) -> None:
        with pytest.raises(NotExposedError):
            deployed_factory.services.expose_off("myapp", "web")


class TestServiceGatewayCLI:
    """Test CLI commands for service gateway expose."""

    def test_cli_prompt_yes_uses_gateway(self, deployed_factory: KuberokuFactory) -> None:
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "expose", "on", "web", "-a", "myapp"],
            input="y\n",
            obj={"factory": deployed_factory},
        )
        assert result.exit_code == 0, result.output
        assert "gateway" in result.output.lower()

    def test_cli_prompt_no_uses_loadbalancer(self, deployed_factory: KuberokuFactory) -> None:
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "expose", "on", "web", "-a", "myapp"],
            input="n\n",
            obj={"factory": deployed_factory},
        )
        assert result.exit_code == 0, result.output
        assert "LoadBalancer" in result.output or "loadbalancer" in result.output

    def test_cli_method_gateway_skips_prompt(self, deployed_factory: KuberokuFactory) -> None:
        runner = CliRunner()
        result = runner.invoke(
            cli,
            [
                "services",
                "expose",
                "on",
                "web",
                "-a",
                "myapp",
                "--method",
                "gateway",
            ],
            obj={"factory": deployed_factory},
        )
        assert result.exit_code == 0, result.output
        assert "gateway" in result.output.lower()
        # No prompt text in output
        assert "random port" not in result.output.lower()

    def test_cli_method_loadbalancer_skips_prompt(self, deployed_factory: KuberokuFactory) -> None:
        runner = CliRunner()
        result = runner.invoke(
            cli,
            [
                "services",
                "expose",
                "on",
                "web",
                "-a",
                "myapp",
                "--method",
                "loadbalancer",
            ],
            obj={"factory": deployed_factory},
        )
        assert result.exit_code == 0, result.output
        assert "random port" not in result.output.lower()

    def test_cli_expose_off_gateway(self, deployed_factory: KuberokuFactory) -> None:
        deployed_factory.services.expose_on("myapp", "web", method="gateway")
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "expose", "off", "web", "-a", "myapp"],
            obj={"factory": deployed_factory},
        )
        assert result.exit_code == 0, result.output
        assert "Unexposed" in result.output or "ClusterIP" in result.output
