"""Tests for services:open — URL resolution + CLI."""

from __future__ import annotations

from unittest.mock import patch

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.exceptions import AppNotFoundError
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import resources
from tests.backends.fake import FakeK8sClient


def _make_factory(fake_k8s: FakeK8sClient, ns: str = "test") -> KuberokuFactory:
    return KuberokuFactory(k8s_client=fake_k8s, namespace=ns)


def _create_app_with_deploy(
    factory: KuberokuFactory,
    app: str = "myapp",
    port: int = 8080,
) -> None:
    """Create an app and deploy with a web process."""
    factory.apps.create(app)
    factory.deploy.deploy(
        app,
        image="nginx:latest",
        ports=[{"containerPort": port, "protocol": "TCP"}],
        wait=False,
    )


class TestOpenUrl:
    """SDK tests for open_url() resolution."""

    def test_open_url_with_domain(self, fake_k8s: FakeK8sClient) -> None:
        """App with ingress domain (no TLS) returns http://domain."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=8080)

        # Add ingress class + domain (no TLS)
        fake_k8s.create_ingress_class({"metadata": {"name": "nginx"}})
        factory.domains.add("myapp", "myapp.example.com", no_tls=True)

        url = factory.services.open_url("myapp")
        assert url == "http://myapp.example.com"

    def test_open_url_with_tls_domain(self, fake_k8s: FakeK8sClient) -> None:
        """App with TLS ingress domain returns https://domain."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=8080)

        # Need ingress class + cert-manager for TLS
        fake_k8s.create_ingress_class({"metadata": {"name": "nginx"}})
        # Install cert-manager (deployment in cert-manager namespace)
        fake_k8s.create_deployment(
            "cert-manager",
            {
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {"name": "cert-manager"},
                "spec": {
                    "replicas": 1,
                    "selector": {"matchLabels": {"app": "cert-manager"}},
                    "template": {
                        "metadata": {"labels": {"app": "cert-manager"}},
                        "spec": {"containers": [{"name": "cm", "image": "cert-manager:v1.14"}]},
                    },
                },
            },
        )

        factory.domains.add("myapp", "myapp.example.com")

        url = factory.services.open_url("myapp")
        assert url == "https://myapp.example.com"

    def test_open_url_with_gateway(self, fake_k8s: FakeK8sClient) -> None:
        """App exposed via gateway returns http://{ip}:{port}."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=8080)

        factory.services.expose_on("myapp", "web", method="gateway")

        # Inject external IP on the gateway LB service
        gw_svc_name = f"{factory.prefix}-gw-10000"
        gw_svc = fake_k8s.get_service("test", gw_svc_name)
        gw_svc["status"] = {"loadBalancer": {"ingress": [{"ip": "1.2.3.4"}]}}
        fake_k8s.update_service("test", gw_svc_name, gw_svc)

        url = factory.services.open_url("myapp")
        assert url == "http://1.2.3.4:10000"

    def test_open_url_with_lb(self, fake_k8s: FakeK8sClient) -> None:
        """App exposed via LoadBalancer returns http://{ip}:{port}."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=8080)

        factory.services.expose_on("myapp", "web", method="loadbalancer")

        # Inject external IP on the process service
        svc_name = resources.safe_name(factory.prefix, "myapp", "web")
        svc = fake_k8s.get_service("test", svc_name)
        svc["status"] = {"loadBalancer": {"ingress": [{"ip": "5.6.7.8"}]}}
        fake_k8s.update_service("test", svc_name, svc)

        url = factory.services.open_url("myapp")
        assert url == "http://5.6.7.8:8080"

    def test_open_url_fallback_localhost(self, fake_k8s: FakeK8sClient) -> None:
        """App with no external access returns http://localhost:{port}."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=3000)

        url = factory.services.open_url("myapp")
        assert url == "http://localhost:3000"

    def test_open_url_app_not_found(self, fake_k8s: FakeK8sClient) -> None:
        """Raises AppNotFoundError for nonexistent app."""
        factory = _make_factory(fake_k8s)
        with pytest.raises(AppNotFoundError):
            factory.services.open_url("no-such-app")

    def test_open_url_default_process_type(self, fake_k8s: FakeK8sClient) -> None:
        """Default process type is 'web'."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=9090)

        url = factory.services.open_url("myapp")
        assert url == "http://localhost:9090"

    def test_open_url_custom_process_type(self, fake_k8s: FakeK8sClient) -> None:
        """Custom process type checks that specific service."""
        factory = _make_factory(fake_k8s)
        _create_app_with_deploy(factory, "myapp", port=8080)

        # open_url for nonexistent process type falls back to localhost:8080
        url = factory.services.open_url("myapp", process_type="admin")
        assert url == "http://localhost:8080"


class TestOpenCLI:
    """CLI tests for services:open."""

    def test_cli_open_invokes_webbrowser(self, cli_runner: CliRunner) -> None:
        """CLI calls webbrowser.open with the resolved URL."""
        fake = FakeK8sClient()
        factory = _make_factory(fake)
        _create_app_with_deploy(factory, "myapp", port=8080)

        with patch("webbrowser.open") as mock_wb_open:
            result = cli_runner.invoke(
                cli,
                ["services", "open", "--app", "myapp"],
                obj={"factory": factory},
            )
            assert result.exit_code == 0
            assert "Opening" in result.output
            mock_wb_open.assert_called_once_with("http://localhost:8080")

    def test_cli_open_colon_syntax(self, cli_runner: CliRunner) -> None:
        """services:open colon syntax works."""
        fake = FakeK8sClient()
        factory = _make_factory(fake)
        _create_app_with_deploy(factory, "myapp", port=8080)

        with patch("webbrowser.open") as mock_wb_open:
            result = cli_runner.invoke(
                cli,
                ["services:open", "--app", "myapp"],
                obj={"factory": factory},
            )
            assert result.exit_code == 0
            mock_wb_open.assert_called_once()

    def test_cli_open_default_process_type(self, cli_runner: CliRunner) -> None:
        """Default process type is 'web'."""
        fake = FakeK8sClient()
        factory = _make_factory(fake)
        _create_app_with_deploy(factory, "myapp", port=8080)

        with patch("webbrowser.open") as mock_wb_open:
            result = cli_runner.invoke(
                cli,
                ["services", "open", "--app", "myapp"],
                obj={"factory": factory},
            )
            assert result.exit_code == 0
            mock_wb_open.assert_called_once_with("http://localhost:8080")
