"""Tests for session module."""

from unittest.mock import MagicMock, patch

from smriti.session import kill_latest, list_sessions
from smriti.utils import exclude_cc_sessions, get_latest_from, only_cc_sessions


def _mock_list(mock_output, **kwargs):
    """Run list_sessions with mocked subprocess and _get_session_cwd."""
    mock_result = MagicMock(stdout=mock_output, returncode=kwargs.get("returncode", 0))
    with (
        patch("smriti.session.subprocess.run", return_value=mock_result),
        patch("smriti.session._get_session_cwd", return_value=None),
    ):
        return list_sessions(**{k: v for k, v in kwargs.items() if k != "returncode"})


def test_list_sessions_parsing():
    """Verify screen -ls output parsing with real format (includes date/time)."""
    sessions = _mock_list(
        "There are screens on:\n"
        "\t12345.gpu01-vajra-a3f1-session-20260212-143022\t"
        "(02/12/2026 03:08:39 PM)\t(Detached)\n"
        "\t12346.gpu01-vajra-a3f1-dev-20260212-150000\t"
        "(02/12/2026 03:00:00 PM)\t(Attached)\n"
        "2 Sockets in /run/screen/S-user.\n"
    )
    assert len(sessions) == 2
    assert sessions[0]["pid"] == "12345"
    assert sessions[0]["name"] == "gpu01-vajra-a3f1-session-20260212-143022"
    assert sessions[0]["state"] == "Detached"
    assert sessions[0]["timestamp"] == "02/12/2026 03:08:39 PM"
    assert sessions[1]["state"] == "Attached"


def test_list_sessions_simple_format():
    """Also handle simple format without date (some screen versions)."""
    sessions = _mock_list(
        "There are screens on:\n"
        "\t12345.gpu01-vajra-a3f1-session-20260212-143022\t(Detached)\n"
        "1 Socket in /run/screen/S-user.\n"
    )
    assert len(sessions) == 1
    assert sessions[0]["name"] == "gpu01-vajra-a3f1-session-20260212-143022"
    assert sessions[0]["state"] == "Detached"
    assert sessions[0]["timestamp"] is None


def test_list_sessions_with_pattern():
    """Filter sessions by pattern."""
    sessions = _mock_list(
        "There are screens on:\n"
        "\t12345.gpu01-vajra-a3f1-session-20260212-143022\t"
        "(02/12/2026 03:08:39 PM)\t(Detached)\n"
        "\t12346.gpu01-vajra-a3f1-cc-20260212-150000\t"
        "(02/12/2026 03:00:00 PM)\t(Detached)\n"
        "\t12347.gpu01-niti-b7c2-session-20260212-160000\t"
        "(02/12/2026 04:00:00 PM)\t(Detached)\n"
        "3 Sockets in /run/screen/S-user.\n",
        pattern="gpu01-vajra-a3f1-cc-",
    )
    assert len(sessions) == 1
    assert sessions[0]["name"] == "gpu01-vajra-a3f1-cc-20260212-150000"


def test_list_sessions_skips_legacy():
    """Non-smriti session names (e.g. pts-37.perelman) are excluded."""
    sessions = _mock_list(
        "There are screens on:\n"
        "\t1017593.pts-37.perelman\t"
        "(02/12/2026 02:45:37 PM)\t(Attached)\n"
        "\t1161441.perelman-aagrawal360-b1c2-session-20260212-150839\t"
        "(02/12/2026 03:08:39 PM)\t(Detached)\n"
        "2 Sockets in /run/screen/S-user.\n"
    )
    assert len(sessions) == 1
    assert sessions[0]["name"] == "perelman-aagrawal360-b1c2-session-20260212-150839"


def test_list_sessions_empty():
    """No sessions should return empty list."""
    sessions = _mock_list(
        "No Sockets found in /run/screen/S-user.\n",
        returncode=1,
    )
    assert sessions == []


def test_list_sessions_cwd():
    """list_sessions should include cwd from _get_session_cwd."""
    mock_result = MagicMock(
        stdout=(
            "There are screens on:\n"
            "\t12345.gpu01-vajra-a3f1-session-20260212-143022\t"
            "(02/12/2026 03:08:39 PM)\t(Detached)\n"
            "1 Socket in /run/screen/S-user.\n"
        ),
        returncode=0,
    )
    with (
        patch("smriti.session.subprocess.run", return_value=mock_result),
        patch(
            "smriti.session._get_session_cwd",
            return_value="/home/user/projects/vajra",
        ),
    ):
        sessions = list_sessions()

    assert sessions[0]["cwd"] == "/home/user/projects/vajra"


def test_kill_latest_kills_most_recent():
    """kill_latest should kill the most recent detached session."""
    mock_ls = MagicMock(
        stdout=(
            "There are screens on:\n"
            "\t111.gpu01-vajra-a3f1-session-20260212-100000\t"
            "(02/12/2026 10:00:00 AM)\t(Detached)\n"
            "\t222.gpu01-vajra-a3f1-session-20260212-120000\t"
            "(02/12/2026 12:00:00 PM)\t(Detached)\n"
            "2 Sockets in /run/screen/S-user.\n"
        ),
        returncode=0,
    )
    mock_quit = MagicMock(returncode=0)

    with (
        patch("smriti.session.subprocess.run", side_effect=[mock_ls, mock_quit]) as mock_run,
        patch("smriti.session._get_session_cwd", return_value=None),
    ):
        result = kill_latest()

    assert result is True
    # Should kill the later one (20260212-120000)
    mock_run.assert_called_with(
        ["screen", "-S", "gpu01-vajra-a3f1-session-20260212-120000", "-X", "quit"]
    )


def test_kill_latest_returns_false_when_empty():
    """kill_latest returns False when no sessions exist."""
    mock_result = MagicMock(stdout="No Sockets found.\n", returncode=1)
    with patch("smriti.session.subprocess.run", return_value=mock_result):
        result = kill_latest()

    assert result is False


# --- exclude_cc_sessions / get_latest_from ---


def test_exclude_cc_sessions():
    """Mixed session list → only non-CC returned."""
    sessions = [
        {"name": "gpu01-vajra-a3f1-session-20260212-143022", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-cc-20260212-150000", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-dev-20260212-160000", "state": "Detached"},
    ]
    filtered = exclude_cc_sessions(sessions)
    assert len(filtered) == 2
    names = [s["name"] for s in filtered]
    assert "gpu01-vajra-a3f1-cc-20260212-150000" not in names


def test_exclude_cc_sessions_preserves_custom_tags():
    """Sessions with tags like dev, debug are kept."""
    sessions = [
        {"name": "gpu01-vajra-a3f1-dev-20260212-143022", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-debug-20260212-150000", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-cc-20260212-160000", "state": "Detached"},
    ]
    filtered = exclude_cc_sessions(sessions)
    assert len(filtered) == 2
    tags = [s["name"] for s in filtered]
    assert "gpu01-vajra-a3f1-dev-20260212-143022" in tags
    assert "gpu01-vajra-a3f1-debug-20260212-150000" in tags


def test_get_latest_from_prefers_detached():
    """Detached session preferred over attached."""
    sessions = [
        {"name": "gpu01-vajra-a3f1-session-20260212-160000", "state": "Attached"},
        {"name": "gpu01-vajra-a3f1-session-20260212-143022", "state": "Detached"},
    ]
    latest = get_latest_from(sessions)
    assert latest is not None
    assert latest["name"] == "gpu01-vajra-a3f1-session-20260212-143022"


def test_get_latest_from_empty():
    """Returns None for empty list."""
    assert get_latest_from([]) is None


# --- only_cc_sessions ---


def test_only_cc_sessions():
    """Mixed session list → only CC returned."""
    sessions = [
        {"name": "gpu01-vajra-a3f1-session-20260212-143022", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-cc-20260212-150000", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-dev-20260212-160000", "state": "Detached"},
    ]
    filtered = only_cc_sessions(sessions)
    assert len(filtered) == 1
    assert filtered[0]["name"] == "gpu01-vajra-a3f1-cc-20260212-150000"


def test_only_cc_sessions_empty_when_none():
    """Returns empty list when no CC sessions."""
    sessions = [
        {"name": "gpu01-vajra-a3f1-session-20260212-143022", "state": "Detached"},
        {"name": "gpu01-vajra-a3f1-dev-20260212-160000", "state": "Detached"},
    ]
    filtered = only_cc_sessions(sessions)
    assert filtered == []
