"""Tests for ``ilum logs`` command."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from typer.testing import CliRunner

from ilum.cli.logs_cmd import _pod_health_key, _select_best_pod
from ilum.cli.main import app
from ilum.core.kubernetes import PodStatus
from ilum.errors import ClusterConnectionError


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


def _make_pod(
    name: str = "ilum-core-abc",
    namespace: str = "default",
    containers: tuple[str, ...] = ("core",),
    phase: str = "Running",
    ready: bool = True,
) -> PodStatus:
    return PodStatus(
        name=name,
        namespace=namespace,
        phase=phase,
        ready=ready,
        restart_count=0,
        containers=containers,
    )


@pytest.fixture()
def mock_k8s() -> MagicMock:
    from ilum.core.kubernetes import KubeClient

    k8s = MagicMock(spec=KubeClient)
    k8s.list_pods_by_label.return_value = [_make_pod()]
    k8s.read_pod_log.return_value = "log line 1\nlog line 2\n"
    k8s.stream_pod_log.return_value = iter(["stream line 1", "stream line 2"])
    return k8s


class TestLogsCommand:
    def test_logs_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["logs", "--help"])
        assert result.exit_code == 0
        assert "Show logs" in result.output
        assert "--follow" in result.output
        assert "--tail" in result.output
        assert "--container" in result.output
        assert "--previous" in result.output

    def test_basic_fetch(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0
        assert "log line 1" in result.output
        mock_k8s.read_pod_log.assert_called_once()
        call_kw = mock_k8s.read_pod_log.call_args
        assert call_kw.kwargs.get("tail_lines") == 100

    def test_tail_custom(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "--tail", "50"])
        assert result.exit_code == 0
        call_kw = mock_k8s.read_pod_log.call_args
        assert call_kw.kwargs.get("tail_lines") == 50

    def test_tail_zero_means_all(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "--tail", "0"])
        assert result.exit_code == 0
        call_kw = mock_k8s.read_pod_log.call_args
        assert call_kw.kwargs.get("tail_lines") is None

    def test_container_flag(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [_make_pod(containers=("core", "sidecar"))]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "-c", "sidecar"])
        assert result.exit_code == 0
        call_kw = mock_k8s.read_pod_log.call_args
        assert call_kw.kwargs.get("container") == "sidecar"

    def test_invalid_container(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [_make_pod(containers=("core",))]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "-c", "nonexistent"])
        assert result.exit_code == 1
        assert "nonexistent" in result.output
        assert "Available containers" in result.output

    def test_previous_flag(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "--previous"])
        assert result.exit_code == 0
        call_kw = mock_k8s.read_pod_log.call_args
        assert call_kw.kwargs.get("previous") is True

    def test_follow_mode(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core", "--follow"])
        assert result.exit_code == 0
        mock_k8s.stream_pod_log.assert_called_once()
        assert "stream line 1" in result.output

    def test_unknown_module(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["logs", "nonexistent"])
        assert result.exit_code == 1

    def test_no_pod_label(self, runner: CliRunner) -> None:
        # kafka module exists but has a pod_label; use a mock to simulate no pod_label
        with patch("ilum.cli.logs_cmd.ModuleResolver") as mock_resolver:
            mod = MagicMock()
            mod.pod_label = ""
            mock_resolver.return_value.get.return_value = mod
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 1
        assert "no pod label" in result.output

    def test_no_pods_found(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = []
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 1
        assert "No pods found" in result.output

    def test_multiple_pods(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [
            _make_pod(name="ilum-core-aaa"),
            _make_pod(name="ilum-core-bbb"),
        ]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0
        assert "Multiple pods" in result.output
        assert "ilum-core-aaa" in result.output

    def test_multi_container_hint(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [_make_pod(containers=("core", "sidecar"))]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0
        assert "multiple containers" in result.output
        assert "Use -c" in result.output

    def test_empty_output(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.read_pod_log.return_value = ""
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0

    def test_connection_error(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.side_effect = ClusterConnectionError(
            "Failed to list pods", suggestion="Check connectivity."
        )
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 1
        assert "Failed to list pods" in result.output

    def test_prefers_healthy_pod_over_pending(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        """When a Pending pod sorts first alphabetically, the healthy pod is still chosen."""
        mock_k8s.list_pods_by_label.return_value = [
            _make_pod(name="aaa-initbad", phase="Pending", ready=False),
            _make_pod(name="zzz-healthy", phase="Running", ready=True),
        ]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0
        mock_k8s.read_pod_log.assert_called_once()
        assert mock_k8s.read_pod_log.call_args[0][1] == "zzz-healthy"

    def test_prefers_ready_over_not_ready(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [
            _make_pod(name="aaa-not-ready", phase="Running", ready=False),
            _make_pod(name="zzz-ready", phase="Running", ready=True),
        ]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        assert result.exit_code == 0
        assert mock_k8s.read_pod_log.call_args[0][1] == "zzz-ready"

    def test_warns_when_no_healthy_pod(self, runner: CliRunner, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [
            _make_pod(name="pod-a", phase="Pending", ready=False),
            _make_pod(name="pod-b", phase="Pending", ready=False),
        ]
        with patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s):
            result = runner.invoke(app, ["logs", "core"])
        # Normalise Rich line-wrapping whitespace for assertion
        flat = " ".join(result.output.split())
        assert "No healthy pod" in flat
        assert "pod-a (Pending)" in flat
        assert "pod-b (Pending)" in flat


class TestPodSelection:
    """Unit tests for _select_best_pod and _pod_health_key."""

    def test_running_ready_first(self) -> None:
        healthy = _make_pod(name="healthy", phase="Running", ready=True)
        pending = _make_pod(name="aaa-pending", phase="Pending", ready=False)
        assert _select_best_pod([pending, healthy]) is healthy

    def test_running_not_ready_before_pending(self) -> None:
        running = _make_pod(name="running", phase="Running", ready=False)
        pending = _make_pod(name="aaa-pending", phase="Pending", ready=False)
        assert _select_best_pod([pending, running]) is running

    def test_alphabetical_tiebreak(self) -> None:
        pod_a = _make_pod(name="aaa", phase="Running", ready=True)
        pod_b = _make_pod(name="bbb", phase="Running", ready=True)
        assert _select_best_pod([pod_b, pod_a]) is pod_a

    def test_single_pod(self) -> None:
        pod = _make_pod(name="only-one", phase="Pending", ready=False)
        assert _select_best_pod([pod]) is pod

    def test_health_key_ordering(self) -> None:
        ready = _pod_health_key(_make_pod(phase="Running", ready=True))
        running = _pod_health_key(_make_pod(phase="Running", ready=False))
        pending = _pod_health_key(_make_pod(phase="Pending", ready=False))
        assert ready < running < pending
