from __future__ import annotations

from pathlib import Path

import pytest

from reddit_mcp import cookie_manager


def _candidate(
    *,
    profile_name: str = "Profile 1",
    rows: int = 0,
    mtime: float = 123.0,
) -> cookie_manager.BrowserProfileCandidate:
    return cookie_manager.BrowserProfileCandidate(
        browser_label="Google Chrome",
        executable_path=Path("."),
        user_data_dir=Path("."),
        profile_name=profile_name,
        profile_dir=Path("."),
        cookies_db=Path("."),
        reddit_cookie_rows=rows,
        cookies_mtime=mtime,
    )


@pytest.mark.anyio
async def test_setup_cookies_uses_playwright_collector_and_persists_when_valid(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    called: dict[str, object] = {}

    async def fake_collect(*, silent: bool) -> dict[str, str]:
        called["silent"] = silent
        return {"reddit_session": "session-token", "csrf_token": "csrf-token"}

    async def fake_valid(_cookies: dict[str, str]) -> bool:
        return True

    monkeypatch.setattr(cookie_manager, "_collect_reddit_cookies_via_playwright", fake_collect)
    monkeypatch.setattr(
        cookie_manager,
        "_shutdown_browser_processes_if_requested",
        lambda *, silent: called.setdefault("shutdown", silent),
    )
    monkeypatch.setattr(cookie_manager, "check_cookies_valid", fake_valid)
    monkeypatch.setattr(cookie_manager, "_save_cookies", lambda cookies: called.setdefault("saved", cookies))
    monkeypatch.setattr(cookie_manager, "save_meta", lambda meta: called.setdefault("meta", meta))

    cookies = await cookie_manager.setup_cookies(silent=True, shutdown_browser=True)

    assert called.get("silent") is True
    assert called.get("shutdown") is True
    assert called.get("saved") == cookies
    assert isinstance(called.get("meta"), dict)
    assert "cookies_created_at" in called["meta"]
    assert cookies["reddit_session"] == "session-token"


@pytest.mark.anyio
async def test_setup_cookies_does_not_persist_when_validation_fails(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    called: dict[str, object] = {}

    async def fake_collect(*, silent: bool) -> dict[str, str]:
        called["silent"] = silent
        return {"reddit_session": "invalid-token"}

    async def fake_valid(_cookies: dict[str, str]) -> bool:
        return False

    monkeypatch.setattr(cookie_manager, "_collect_reddit_cookies_via_playwright", fake_collect)
    monkeypatch.setattr(
        cookie_manager,
        "_shutdown_browser_processes_if_requested",
        lambda *, silent: called.setdefault("shutdown", silent),
    )
    monkeypatch.setattr(cookie_manager, "check_cookies_valid", fake_valid)
    monkeypatch.setattr(cookie_manager, "_save_cookies", lambda cookies: called.setdefault("saved", cookies))

    with pytest.raises(RuntimeError, match="validation failed"):
        await cookie_manager.setup_cookies(silent=True, shutdown_browser=True)

    assert called.get("silent") is True
    assert "saved" not in called


def test_shutdown_browser_processes_is_noop_off_windows(monkeypatch: pytest.MonkeyPatch) -> None:
    messages: list[str] = []

    monkeypatch.setattr(cookie_manager.sys, "platform", "darwin")
    monkeypatch.setattr(cookie_manager, "console_print", lambda message: messages.append(message))

    cookie_manager._shutdown_browser_processes_if_requested(silent=False)

    assert "Windows profile-lock issues" in messages[0]


def test_shutdown_browser_processes_raises_when_windows_shutdown_leaves_processes(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    states = [["chrome"], ["chrome"]]
    monkeypatch.setattr(cookie_manager, "_list_running_windows_browser_processes", lambda: states.pop(0))
    monkeypatch.setattr(cookie_manager, "_run_windows_browser_shutdown", lambda: None)

    with pytest.raises(RuntimeError, match="Could not fully close browser processes automatically"):
        cookie_manager._shutdown_browser_processes_if_requested(silent=True)


def test_shutdown_browser_processes_only_targets_chrome_and_edge(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    script_payload: dict[str, str] = {}

    def fake_run(args, **kwargs):  # type: ignore[no-untyped-def]
        script_payload["script"] = args[-1]

        class Result:
            returncode = 0
            stdout = "[]"

        return Result()

    monkeypatch.setattr(cookie_manager.subprocess, "run", fake_run)

    assert cookie_manager._list_running_windows_browser_processes() == []
    assert "'chrome'" in script_payload["script"]
    assert "'msedge'" in script_payload["script"]
    assert "chromium" not in script_payload["script"]


def test_windows_locked_profile_error_points_users_to_shutdown_browser_flag() -> None:
    candidate = _candidate()
    original_platform = cookie_manager.sys.platform
    cookie_manager.sys.platform = "win32"
    try:
        error = cookie_manager._copy_locked_profile_error(candidate, Path("Network/Cookies"))
    finally:
        cookie_manager.sys.platform = original_platform

    message = str(error)
    assert "--shutdown-browser" in message
    assert "save any unfinished work in Chrome or Edge" in message
    assert "AI assistant" in message


def test_filter_reddit_cookies_keeps_only_reddit_domains() -> None:
    all_cookies = [
        {"name": "reddit_session", "value": "a", "domain": ".reddit.com"},
        {"name": "csrf_token", "value": "b", "domain": "reddit.com"},
        {"name": "other", "value": "c", "domain": ".example.com"},
    ]

    result = cookie_manager._filter_reddit_cookies(all_cookies)

    assert result == {"reddit_session": "a", "csrf_token": "b"}


@pytest.mark.anyio
async def test_collect_silent_mode_never_waits_for_source_profile_session(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate(rows=2)
    reads: list[str] = []
    waited: list[bool] = []

    async def fake_read(
        selected: cookie_manager.BrowserProfileCandidate,
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        reads.append(selected.profile_name)
        return {}

    async def fake_wait_for_source_profile_session(
        _candidates: list[cookie_manager.BrowserProfileCandidate],
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        waited.append(True)
        return {}

    monkeypatch.setattr(cookie_manager, "_discover_browser_profile_candidates", lambda: [candidate])
    monkeypatch.setattr(cookie_manager, "_read_cookies_from_profile_copy", fake_read)
    monkeypatch.setattr(
        cookie_manager,
        "_wait_for_source_profile_session",
        fake_wait_for_source_profile_session,
    )

    with pytest.raises(RuntimeError, match="No reusable Reddit session found"):
        await cookie_manager._collect_reddit_cookies_via_playwright(silent=True)

    assert reads == ["Profile 1"]
    assert waited == []


@pytest.mark.anyio
async def test_collect_non_silent_waits_for_source_profile_session_when_no_reusable(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate(profile_name="Profile 4", rows=0, mtime=456.0)
    waited: list[list[str]] = []

    async def fake_wait_for_source_profile_session(
        candidates: list[cookie_manager.BrowserProfileCandidate],
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        waited.append([c.profile_name for c in candidates])
        return {"reddit_session": "session-token"}

    monkeypatch.setattr(cookie_manager, "_discover_browser_profile_candidates", lambda: [candidate])
    monkeypatch.setattr(cookie_manager, "_should_hint_windows_shutdown_browser", lambda _candidates: False)
    monkeypatch.setattr(
        cookie_manager,
        "_wait_for_source_profile_session",
        fake_wait_for_source_profile_session,
    )

    cookies = await cookie_manager._collect_reddit_cookies_via_playwright(silent=False)

    assert cookies["reddit_session"] == "session-token"
    assert waited == [["Profile 4"]]


@pytest.mark.anyio
async def test_collect_non_silent_windows_lock_hint_skips_wait_and_points_to_shutdown_browser(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate(profile_name="Profile 4", rows=0, mtime=456.0)
    waited: list[bool] = []

    async def fake_wait_for_source_profile_session(
        candidates: list[cookie_manager.BrowserProfileCandidate],
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del candidates, async_playwright
        waited.append(True)
        return {}

    monkeypatch.setattr(cookie_manager, "_discover_browser_profile_candidates", lambda: [candidate])
    monkeypatch.setattr(cookie_manager, "_should_hint_windows_shutdown_browser", lambda _candidates: True)
    monkeypatch.setattr(
        cookie_manager,
        "_wait_for_source_profile_session",
        fake_wait_for_source_profile_session,
    )

    with pytest.raises(RuntimeError, match="--shutdown-browser"):
        await cookie_manager._collect_reddit_cookies_via_playwright(silent=False)

    assert waited == []


def test_should_hint_windows_shutdown_browser_only_when_windows_and_browser_running(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate(profile_name="Profile 4", rows=0, mtime=456.0)

    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    monkeypatch.setattr(cookie_manager, "_list_running_windows_browser_processes", lambda: ["chrome"])
    assert cookie_manager._should_hint_windows_shutdown_browser([candidate]) is True

    monkeypatch.setattr(cookie_manager, "_list_running_windows_browser_processes", lambda: [])
    assert cookie_manager._should_hint_windows_shutdown_browser([candidate]) is False

    monkeypatch.setattr(cookie_manager.sys, "platform", "darwin")
    monkeypatch.setattr(cookie_manager, "_list_running_windows_browser_processes", lambda: ["chrome"])
    assert cookie_manager._should_hint_windows_shutdown_browser([candidate]) is False


@pytest.mark.anyio
async def test_collect_non_silent_errors_when_no_browser_profile_found(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(cookie_manager, "_discover_browser_profile_candidates", lambda: [])

    with pytest.raises(RuntimeError, match="No compatible local Chrome/Edge profile found"):
        await cookie_manager._collect_reddit_cookies_via_playwright(silent=False)


@pytest.mark.anyio
async def test_collect_non_silent_errors_when_source_profile_session_times_out(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate(profile_name="Profile 7", rows=0, mtime=789.0)

    async def fake_wait_for_source_profile_session(
        _candidates: list[cookie_manager.BrowserProfileCandidate],
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        return {}

    monkeypatch.setattr(cookie_manager, "_discover_browser_profile_candidates", lambda: [candidate])
    monkeypatch.setattr(
        cookie_manager,
        "_wait_for_source_profile_session",
        fake_wait_for_source_profile_session,
    )

    with pytest.raises(RuntimeError, match="No reusable Reddit session appeared"):
        await cookie_manager._collect_reddit_cookies_via_playwright(silent=False)


@pytest.mark.anyio
async def test_wait_for_source_profile_session_checks_all_candidates_until_found(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    first = _candidate(profile_name="Profile 1")
    second = _candidate(profile_name="Profile 2")
    calls: list[str] = []

    async def fake_read(
        selected: cookie_manager.BrowserProfileCandidate,
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        calls.append(selected.profile_name)
        if selected.profile_name == "Profile 2":
            return {"reddit_session": "session-token"}
        return {}

    monkeypatch.setattr(cookie_manager, "_read_cookies_from_profile_copy", fake_read)

    cookies = await cookie_manager._wait_for_source_profile_session(
        [first, second],
        async_playwright=object(),
    )

    assert cookies["reddit_session"] == "session-token"
    assert calls == ["Profile 1", "Profile 2"]


@pytest.mark.anyio
async def test_wait_for_source_profile_session_returns_empty_on_timeout(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candidate = _candidate()
    calls: list[str] = []

    async def fake_read(
        selected: cookie_manager.BrowserProfileCandidate,
        *,
        async_playwright: object,
    ) -> dict[str, str]:
        del async_playwright
        calls.append(selected.profile_name)
        return {}

    monkeypatch.setattr(cookie_manager, "_read_cookies_from_profile_copy", fake_read)
    monkeypatch.setattr(cookie_manager, "LOGIN_WAIT_TIMEOUT_SECONDS", 0.01)
    monkeypatch.setattr(cookie_manager, "SOURCE_PROFILE_POLL_INTERVAL_MS", 1)

    cookies = await cookie_manager._wait_for_source_profile_session(
        [candidate],
        async_playwright=object(),
    )

    assert cookies == {}
    assert calls
