"""Tests for services:exec — SDK layer (interactive + detached)."""

from __future__ import annotations

import pytest

from kuberoku.exceptions import (
    AppNotFoundError,
    DynoNotFoundError,
    NoDynosError,
    ProcessTypeNotFoundError,
)
from kuberoku.factory import KuberokuFactory
from kuberoku.models import DetachedJob, ExecResult
from tests.backends.fake import FakeK8sClient


class TestExecInteractive:
    def test_exec_in_web_pod(self, deployed_factory_with_pods: KuberokuFactory) -> None:
        k8s = deployed_factory_with_pods.k8s
        assert isinstance(k8s, FakeK8sClient)
        # Inject output for the first pod (sorted alphabetically)
        pods = k8s.list_pods(deployed_factory_with_pods.namespace)
        pod_names = sorted(p["metadata"]["name"] for p in pods)
        k8s.inject_exec_output(pod_names[0], "hello world\n")
        k8s.exec_calls.clear()

        result = deployed_factory_with_pods.services.exec_interactive("myapp", ["echo", "hello"])
        assert isinstance(result, ExecResult)
        assert result.output == "hello world\n"
        assert result.process_type == "web"
        assert result.pod_name == pod_names[0]
        # Verify the correct pod and command reached K8s
        assert len(k8s.exec_calls) == 1
        assert k8s.exec_calls[0]["name"] == pod_names[0]
        assert k8s.exec_calls[0]["command"] == ["echo", "hello"]
        assert k8s.exec_calls[0]["tty"] is True  # default

    def test_exec_returns_pod_name(self, deployed_factory_with_pods: KuberokuFactory) -> None:
        result = deployed_factory_with_pods.services.exec_interactive("myapp", ["ls"])
        assert result.pod_name  # Non-empty

    def test_exec_specific_dyno_targets_correct_pod(
        self, deployed_factory_with_pods: KuberokuFactory
    ) -> None:
        k8s = deployed_factory_with_pods.k8s
        assert isinstance(k8s, FakeK8sClient)

        # Inject distinct output for pod-b (web.2, since pod-b sorts after pod-a)
        pods = k8s.list_pods(deployed_factory_with_pods.namespace)
        pod_names = sorted(p["metadata"]["name"] for p in pods)
        # pod_names[0] = pod-a = web.1, pod_names[1] = pod-b = web.2
        k8s.inject_exec_output(pod_names[1], "from pod-b\n")
        k8s.exec_calls.clear()

        result = deployed_factory_with_pods.services.exec_interactive(
            "myapp", ["cat", "/var/log/test"], dyno="web.2"
        )
        assert result.process_type == "web"
        assert result.output == "from pod-b\n"
        # Verify exec was called on the correct pod (pod-b)
        assert len(k8s.exec_calls) == 1
        assert k8s.exec_calls[0]["name"] == pod_names[1]
        assert k8s.exec_calls[0]["command"] == ["cat", "/var/log/test"]

    def test_exec_dyno_not_found(self, deployed_factory_with_pods: KuberokuFactory) -> None:
        with pytest.raises(DynoNotFoundError):
            deployed_factory_with_pods.services.exec_interactive("myapp", ["ls"], dyno="web.99")

    def test_exec_no_pods(self, deployed_factory: KuberokuFactory) -> None:
        with pytest.raises(NoDynosError):
            deployed_factory.services.exec_interactive("myapp", ["ls"])

    def test_exec_app_not_found(self, tmp_namespace: str) -> None:
        factory = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=tmp_namespace)
        with pytest.raises(AppNotFoundError):
            factory.services.exec_interactive("noapp", ["ls"])

    def test_exec_custom_process_type(
        self, multi_process_factory_with_pods: KuberokuFactory
    ) -> None:
        result = multi_process_factory_with_pods.services.exec_interactive(
            "myapp", ["ls"], process_type="worker"
        )
        assert result.process_type == "worker"

    def test_exec_default_process_type_is_web(
        self, deployed_factory_with_pods: KuberokuFactory
    ) -> None:
        result = deployed_factory_with_pods.services.exec_interactive("myapp", ["ls"])
        assert result.process_type == "web"

    def test_exec_tty_false_reaches_k8s(self, deployed_factory_with_pods: KuberokuFactory) -> None:
        k8s = deployed_factory_with_pods.k8s
        assert isinstance(k8s, FakeK8sClient)
        k8s.exec_calls.clear()

        result = deployed_factory_with_pods.services.exec_interactive("myapp", ["bash"], tty=False)
        assert isinstance(result, ExecResult)
        # Verify tty=False was passed through to exec_in_pod
        assert len(k8s.exec_calls) == 1
        assert k8s.exec_calls[0]["tty"] is False
        assert k8s.exec_calls[0]["command"] == ["bash"]

    def test_exec_command_list_passed_through(
        self, deployed_factory_with_pods: KuberokuFactory
    ) -> None:
        k8s = deployed_factory_with_pods.k8s
        assert isinstance(k8s, FakeK8sClient)
        k8s.exec_calls.clear()

        deployed_factory_with_pods.services.exec_interactive(
            "myapp", ["/bin/sh", "-c", "echo hello && ls -la"]
        )
        assert len(k8s.exec_calls) == 1
        assert k8s.exec_calls[0]["command"] == ["/bin/sh", "-c", "echo hello && ls -la"]


class TestExecDetached:
    def test_creates_job(self, deployed_factory: KuberokuFactory) -> None:
        result = deployed_factory.services.exec_detached("myapp", ["/bin/sh", "-c", "echo hello"])
        assert isinstance(result, DetachedJob)
        assert result.app == "myapp"
        assert result.command == ["/bin/sh", "-c", "echo hello"]

    def test_job_name_contains_app(self, deployed_factory: KuberokuFactory) -> None:
        result = deployed_factory.services.exec_detached("myapp", ["echo", "test"])
        assert "myapp" in result.job_name

    def test_detached_with_extra_env(self, deployed_factory: KuberokuFactory) -> None:
        result = deployed_factory.services.exec_detached(
            "myapp",
            ["env"],
            extra_env={"MY_VAR": "my_value"},
        )
        assert isinstance(result, DetachedJob)

        # Verify the Job has the extra env
        k8s = deployed_factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        job = k8s._get("Job", deployed_factory.namespace, result.job_name)
        container = job["spec"]["template"]["spec"]["containers"][0]
        env_vars = container.get("env", [])
        assert any(e["name"] == "MY_VAR" and e["value"] == "my_value" for e in env_vars)

    def test_detached_with_timeout(self, deployed_factory: KuberokuFactory) -> None:
        result = deployed_factory.services.exec_detached("myapp", ["sleep", "10"], timeout=60)
        assert isinstance(result, DetachedJob)

        # Verify the Job has the timeout
        k8s = deployed_factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        job = k8s._get("Job", deployed_factory.namespace, result.job_name)
        assert job["spec"]["activeDeadlineSeconds"] == 60

    def test_detached_uses_deployment_image(self, deployed_factory: KuberokuFactory) -> None:
        result = deployed_factory.services.exec_detached("myapp", ["echo", "test"])
        k8s = deployed_factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        job = k8s._get("Job", deployed_factory.namespace, result.job_name)
        image = job["spec"]["template"]["spec"]["containers"][0]["image"]
        assert image == "myapp:v1"

    def test_detached_app_not_found(self, tmp_namespace: str) -> None:
        factory = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=tmp_namespace)
        with pytest.raises(AppNotFoundError):
            factory.services.exec_detached("noapp", ["ls"])

    def test_detached_process_type_not_found(self, deployed_factory: KuberokuFactory) -> None:
        with pytest.raises(ProcessTypeNotFoundError):
            deployed_factory.services.exec_detached("myapp", ["ls"], process_type="nonexistent")

    def test_detached_custom_process_type(self, multi_process_factory: KuberokuFactory) -> None:
        result = multi_process_factory.services.exec_detached(
            "myapp", ["ls"], process_type="worker"
        )
        assert isinstance(result, DetachedJob)
        k8s = multi_process_factory.k8s
        assert isinstance(k8s, FakeK8sClient)
        job = k8s._get("Job", multi_process_factory.namespace, result.job_name)
        image = job["spec"]["template"]["spec"]["containers"][0]["image"]
        assert image == "myapp:v2"
