"""CLI tests for services:logs and services:exec commands."""

from __future__ import annotations

from typing import Any

from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s import labels
from tests.backends.fake import FakeK8sClient


def _factory_with_pods(namespace: str) -> KuberokuFactory:
    """Create a factory with a deployed app and pods with logs."""
    f = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=namespace)
    f.apps.create("myapp")
    f.deploy.deploy("myapp", image="myapp:v1", wait=False)

    k8s = f.k8s
    assert isinstance(k8s, FakeK8sClient)

    # Create a pod
    pod_name = f"{f.prefix}-myapp-web-pod-a"
    pod_labels = labels.for_process(f.domain, f.prefix, "myapp", "web")
    body: dict[str, Any] = {
        "apiVersion": "v1",
        "kind": "Pod",
        "metadata": {"name": pod_name, "labels": pod_labels},
        "spec": {
            "containers": [{"name": "web", "image": "myapp:v1"}],
            "nodeName": "fake-node-1",
        },
        "status": {"phase": "Running"},
    }
    k8s.create_pod(namespace, body)
    k8s.inject_pod_logs(pod_name, ["Starting server...", "Listening on :8080"])
    k8s.inject_exec_output(pod_name, "hello world\n")

    return f


class TestServicesLogsCLI:
    def test_logs_basic(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "web.1" in result.output
        assert "Starting server..." in result.output

    def test_logs_colon_syntax(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services:logs", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "web.1" in result.output

    def test_logs_with_num(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp", "-n", "1"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        # With num=1, should get at most 1 line per pod
        lines = [x for x in result.output.strip().split("\n") if "web." in x]
        assert len(lines) <= 1

    def test_logs_with_type_filter(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp", "--type", "web"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "web.1" in result.output

    def test_logs_with_since(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp", "--since", "5m"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Starting server..." in result.output
        # Verify since was passed to K8s
        k8s = factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        for call in k8s.log_calls:
            assert call["since_seconds"] == 300

    def test_logs_invalid_since(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp", "--since", "xyz"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0

    def test_logs_app_not_found(self, tmp_namespace: str) -> None:
        factory = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "noapp"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0

    def test_logs_output_format(self, tmp_namespace: str) -> None:
        """Output format: {dyno} | {message}."""
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "logs", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        for line in result.output.strip().split("\n"):
            if "|" in line:
                assert " | " in line

    def test_logs_help(self) -> None:
        runner = CliRunner()
        result = runner.invoke(cli, ["services", "logs", "--help"])
        assert result.exit_code == 0
        assert "--tail" in result.output
        assert "--num" in result.output
        assert "--since" in result.output


class TestServicesExecCLI:
    def test_exec_basic(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "exec", "echo", "hello", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "hello world" in result.output

    def test_exec_colon_syntax(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services:exec", "echo", "hello", "--app", "myapp"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

    def test_exec_detached(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "exec", "echo", "hello", "--app", "myapp", "--detach"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "detached" in result.output.lower() or "job" in result.output.lower()

    def test_exec_detached_with_env(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            [
                "services",
                "exec",
                "env",
                "--app",
                "myapp",
                "--detach",
                "--env",
                "FOO=bar",
                "--env",
                "BAZ=qux",
            ],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        # Verify the Job in K8s store has the env vars
        k8s = factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        jobs = k8s._store.list("Job", tmp_namespace)
        assert len(jobs) == 1
        container = jobs[0]["spec"]["template"]["spec"]["containers"][0]
        env_vars = {e["name"]: e["value"] for e in container.get("env", [])}
        assert env_vars["FOO"] == "bar"
        assert env_vars["BAZ"] == "qux"

    def test_exec_detached_with_timeout(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            [
                "services",
                "exec",
                "sleep",
                "60",
                "--app",
                "myapp",
                "--detach",
                "--timeout",
                "120",
            ],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        # Verify the Job in K8s store has the timeout
        k8s = factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        jobs = k8s._store.list("Job", tmp_namespace)
        assert len(jobs) == 1
        assert jobs[0]["spec"]["activeDeadlineSeconds"] == 120

    def test_exec_no_tty(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "exec", "ls", "--app", "myapp", "--no-tty"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        # Verify tty=False was passed through to K8s
        k8s = factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        assert len(k8s.exec_calls) == 1
        assert k8s.exec_calls[0]["tty"] is False
        assert k8s.exec_calls[0]["command"] == ["ls"]

    def test_exec_app_not_found(self, tmp_namespace: str) -> None:
        factory = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["services", "exec", "ls", "--app", "noapp"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0

    def test_exec_help(self) -> None:
        runner = CliRunner()
        result = runner.invoke(cli, ["services", "exec", "--help"])
        assert result.exit_code == 0
        assert "--detach" in result.output
        assert "--no-tty" in result.output
        assert "--env" in result.output
        assert "--timeout" in result.output

    def test_exec_invalid_env_format(self, tmp_namespace: str) -> None:
        factory = _factory_with_pods(tmp_namespace)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            [
                "services",
                "exec",
                "env",
                "--app",
                "myapp",
                "--detach",
                "--env",
                "NOEQUALS",
            ],
            obj={"factory": factory},
        )
        assert result.exit_code != 0
