"""Tests for ``ilum status`` command."""

from __future__ import annotations

from datetime import timedelta
from unittest.mock import MagicMock, patch

import pytest
import typer
from typer.testing import CliRunner

from ilum.cli.main import app
from ilum.cli.status_cmd import _format_age, _parse_duration
from ilum.core.kubernetes import EventInfo, PodStatus
from ilum.core.release import ReleaseInfo
from ilum.errors import ReleaseNotFoundError


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


@pytest.fixture()
def mock_mgr() -> MagicMock:
    from ilum.core.modules import ModuleResolver
    from ilum.core.release import ReleaseManager

    mgr = MagicMock(spec=ReleaseManager)
    mgr.resolver = ModuleResolver()
    mgr.get_enabled_modules.return_value = ["core", "ui", "jupyter"]
    mgr.fetch_computed_values.return_value = {
        "ilum-core": {"enabled": True},
        "ilum-ui": {"enabled": True},
        "ilum-jupyter": {"enabled": True},
    }
    mgr.get_release_info.return_value = ReleaseInfo(
        name="ilum",
        namespace="default",
        status="deployed",
        chart="ilum",
        chart_version="6.7.0",
        revision=3,
        last_deployed="2024-01-15 12:00:00",
    )
    mgr.k8s = MagicMock()
    mgr.k8s.list_pods.return_value = [
        PodStatus(
            name="ilum-core-0", namespace="default", phase="Running", ready=True, restart_count=0
        ),
        PodStatus(
            name="ilum-ui-0", namespace="default", phase="Running", ready=True, restart_count=0
        ),
    ]
    return mgr


def _patch_status(mock_mgr: MagicMock):
    """Patch the ReleaseManager constructor used by the status command."""
    return patch("ilum.cli.status_cmd.ReleaseManager", return_value=mock_mgr)


class TestStatusCommand:
    def test_status_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["status", "--help"])
        assert result.exit_code == 0
        assert "Show the status" in result.output

    def test_status_shows_release_info(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status"])
        assert result.exit_code == 0
        assert "deployed" in result.output
        assert "6.7.0" in result.output

    def test_status_shows_modules(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status"])
        # Rich may wrap the title across lines
        assert "Enabled" in result.output and "Live" in result.output
        assert "core" in result.output
        assert "jupyter" in result.output

    def test_status_shows_pods(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status"])
        assert "ilum-core-0" in result.output
        assert "Running" in result.output

    def test_status_release_not_found(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.get_release_info.side_effect = ReleaseNotFoundError("not found")
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status"])
        assert result.exit_code == 1

    def test_status_no_pods_flag(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--no-pods"])
        assert result.exit_code == 0
        # pods should not be listed
        mock_mgr.k8s.list_pods.assert_not_called()

    def test_status_shows_live_modules(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
            "ilum-jupyter": {"enabled": True},
        }
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--no-pods"])
        assert result.exit_code == 0
        # Rich may wrap the title across lines
        assert "Enabled" in result.output and "Live" in result.output
        assert "core" in result.output

    def test_status_module_drift_warning(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        # Config tracks 'sql' but live doesn't have it
        mock_mgr.get_enabled_modules.return_value = ["core", "ui", "sql"]
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
        }
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--no-pods"])
        assert "Config drift" in result.output
        assert "sql" in result.output

    def test_status_auto_syncs_when_config_empty(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        """When config has no tracked modules but live modules exist, auto-sync."""
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
        }
        mock_mgr.get_enabled_modules.return_value = []
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--no-pods"])
        assert result.exit_code == 0
        assert "Synced" in result.output
        mock_mgr.save_enabled_modules.assert_called_once()
        assert "Config drift" not in result.output

    def test_status_no_modules_detected(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.fetch_computed_values.return_value = {}
        mock_mgr.get_enabled_modules.return_value = []
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--no-pods"])
        assert "No modules detected" in result.output


class TestParseDuration:
    def test_parse_hours(self) -> None:
        assert _parse_duration("1h") == timedelta(hours=1)

    def test_parse_minutes(self) -> None:
        assert _parse_duration("30m") == timedelta(minutes=30)

    def test_parse_seconds(self) -> None:
        assert _parse_duration("45s") == timedelta(seconds=45)

    def test_parse_combined(self) -> None:
        assert _parse_duration("2h30m") == timedelta(hours=2, minutes=30)

    def test_parse_invalid(self) -> None:
        with pytest.raises(typer.BadParameter):
            _parse_duration("invalid")


class TestFormatAge:
    def test_empty_string(self) -> None:
        assert _format_age("") == ""

    def test_invalid_timestamp(self) -> None:
        result = _format_age("not-a-timestamp")
        assert result == "not-a-timestamp"


class TestStatusEvents:
    def test_events_flag_shows_events(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.k8s.list_events.return_value = [
            EventInfo(
                name="ev1",
                namespace="default",
                reason="Pulled",
                message="Successfully pulled image",
                event_type="Normal",
                involved_object="Pod/ilum-core-0",
                count=1,
                first_seen="2024-01-15T12:00:00+00:00",
                last_seen="2024-01-15T12:00:00+00:00",
                source="kubelet",
            ),
        ]
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--events", "--no-modules"])
        assert result.exit_code == 0
        assert "Pulled" in result.output

    def test_events_type_filter(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.k8s.list_events.return_value = [
            EventInfo(
                name="ev1",
                namespace="default",
                reason="Pulled",
                message="normal event",
                event_type="Normal",
                involved_object="Pod/p1",
                count=1,
                first_seen="",
                last_seen="",
                source="",
            ),
            EventInfo(
                name="ev2",
                namespace="default",
                reason="BackOff",
                message="warning event",
                event_type="Warning",
                involved_object="Pod/p2",
                count=3,
                first_seen="",
                last_seen="",
                source="",
            ),
        ]
        with _patch_status(mock_mgr):
            result = runner.invoke(
                app, ["status", "--events", "--events-type", "Warning", "--no-modules"]
            )
        assert result.exit_code == 0
        assert "BackOff" in result.output

    def test_events_none_found(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.k8s.list_events.return_value = []
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["status", "--events", "--no-modules"])
        assert result.exit_code == 0
        assert "No events found" in result.output

    def test_events_json_output(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.k8s.list_events.return_value = [
            EventInfo(
                name="ev1",
                namespace="default",
                reason="Created",
                message="created pod",
                event_type="Normal",
                involved_object="Pod/p1",
                count=1,
                first_seen="",
                last_seen="",
                source="",
            ),
        ]
        with _patch_status(mock_mgr):
            result = runner.invoke(app, ["--output", "json", "status", "--events", "--no-modules"])
        assert result.exit_code == 0
        assert "events" in result.output


class TestStatusWait:
    def test_wait_success(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        """When pods are already ready, --wait exits immediately."""
        with _patch_status(mock_mgr), patch("ilum.cli.status_cmd.time") as mock_time:
            mock_time.monotonic.side_effect = [0, 0, 1]  # start, check, after sleep
            mock_time.sleep = MagicMock()
            result = runner.invoke(app, ["status", "--wait", "--no-modules"])
        assert result.exit_code == 0
        assert "All pods are ready" in result.output

    def test_wait_timeout(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        """When pods never become ready, --wait exits with code 1."""
        mock_mgr.k8s.list_pods.return_value = [
            PodStatus(
                name="ilum-core-0",
                namespace="default",
                phase="Pending",
                ready=False,
                restart_count=0,
            ),
        ]
        with _patch_status(mock_mgr), patch("ilum.cli.status_cmd.time") as mock_time:
            # Start, first check (within deadline), second check (past deadline)
            mock_time.monotonic.side_effect = [0, 0, 301]
            mock_time.sleep = MagicMock()
            result = runner.invoke(
                app, ["status", "--wait", "--wait-timeout", "300", "--no-modules"]
            )
        assert result.exit_code == 1
        assert "Timed out" in result.output

    def test_wait_help_text(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["status", "--help"])
        assert "--wait" in result.output
        assert "--wait-timeout" in result.output
        assert "--events" in result.output
