from __future__ import annotations

import sqlite3
import subprocess
from pathlib import Path

import pytest

from reddit_mcp import cookie_manager


def _write_cookies_db(path: Path, *, reddit_rows: int = 0, other_rows: int = 0) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(path) as conn:
        conn.execute("create table cookies (host_key text)")
        for _ in range(reddit_rows):
            conn.execute("insert into cookies(host_key) values (?)", (".reddit.com",))
        for _ in range(other_rows):
            conn.execute("insert into cookies(host_key) values (?)", (".example.com",))
        conn.commit()


def _candidate(
    *,
    user_data_dir: Path,
    profile_name: str,
    profile_dir: Path,
    cookies_db: Path,
) -> cookie_manager.BrowserProfileCandidate:
    return cookie_manager.BrowserProfileCandidate(
        browser_label="Google Chrome",
        executable_path=user_data_dir / "chrome",
        user_data_dir=user_data_dir,
        profile_name=profile_name,
        profile_dir=profile_dir,
        cookies_db=cookies_db,
        reddit_cookie_rows=1,
        cookies_mtime=123.0,
        cookie_probe_error=None,
    )


def test_discover_browser_profile_candidates_supports_network_cookie_db(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    browser_bin = tmp_path / "chrome"
    browser_bin.write_text("", encoding="utf-8")
    user_data_dir = tmp_path / "User Data"
    profile_dir = user_data_dir / "Profile 3"
    cookies_db = profile_dir / "Network" / "Cookies"
    _write_cookies_db(cookies_db, reddit_rows=2)

    monkeypatch.setattr(
        cookie_manager,
        "_known_browser_installations",
        lambda: [("Google Chrome", browser_bin, user_data_dir)],
    )

    candidates = cookie_manager._discover_browser_profile_candidates()

    assert len(candidates) == 1
    assert candidates[0].cookies_db == cookies_db
    assert candidates[0].reddit_cookie_rows == 2
    assert candidates[0].cookie_probe_error is None


def test_discover_browser_profile_candidates_preserves_probe_errors(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    browser_bin = tmp_path / "chrome"
    browser_bin.write_text("", encoding="utf-8")
    user_data_dir = tmp_path / "User Data"
    profile_dir = user_data_dir / "Profile 5"
    cookies_db = profile_dir / "Network" / "Cookies"
    cookies_db.parent.mkdir(parents=True, exist_ok=True)
    cookies_db.write_text("", encoding="utf-8")

    monkeypatch.setattr(
        cookie_manager,
        "_known_browser_installations",
        lambda: [("Google Chrome", browser_bin, user_data_dir)],
    )
    monkeypatch.setattr(
        cookie_manager,
        "_probe_reddit_cookie_rows",
        lambda _cookies_db: cookie_manager.CookieProbeResult(
            reddit_cookie_rows=0,
            error="database is locked",
        ),
    )

    candidates = cookie_manager._discover_browser_profile_candidates()

    assert len(candidates) == 1
    assert candidates[0].cookies_db == cookies_db
    assert candidates[0].reddit_cookie_rows == 0
    assert candidates[0].cookie_probe_error == "database is locked"


def test_profile_paths_to_copy_is_minimal_and_cookie_focused(tmp_path: Path) -> None:
    user_data_dir = tmp_path / "User Data"
    profile_dir = user_data_dir / "Profile 7"
    cookies_db = profile_dir / "Network" / "Cookies"
    _write_cookies_db(cookies_db, reddit_rows=1)

    candidate = _candidate(
        user_data_dir=user_data_dir,
        profile_name="Profile 7",
        profile_dir=profile_dir,
        cookies_db=cookies_db,
    )

    paths = cookie_manager._profile_paths_to_copy(candidate)

    assert Path("Network/Cookies") in paths
    assert Path("Network/Cookies-wal") in paths
    assert Path("Preferences") in paths
    assert Path("Secure Preferences") in paths
    assert all("Cache" not in str(path) for path in paths)


def test_copy_profile_workspace_copies_minimum_required_files(tmp_path: Path) -> None:
    user_data_dir = tmp_path / "User Data"
    profile_dir = user_data_dir / "Profile 2"
    cookies_db = profile_dir / "Network" / "Cookies"
    _write_cookies_db(cookies_db, reddit_rows=1)
    (profile_dir / "Network" / "Cookies-wal").write_text("wal", encoding="utf-8")
    (profile_dir / "Preferences").write_text("prefs", encoding="utf-8")
    (profile_dir / "Secure Preferences").write_text("secure", encoding="utf-8")
    (profile_dir / "History").write_text("history", encoding="utf-8")
    (profile_dir / "Cache").mkdir(parents=True, exist_ok=True)
    (profile_dir / "Cache" / "tmp.bin").write_text("cache", encoding="utf-8")
    (user_data_dir / "Local State").write_text("{}", encoding="utf-8")

    candidate = _candidate(
        user_data_dir=user_data_dir,
        profile_name="Profile 2",
        profile_dir=profile_dir,
        cookies_db=cookies_db,
    )

    workspace = tmp_path / "workspace"
    cookie_manager._copy_profile_workspace(candidate, workspace)

    copied_profile = workspace / "Profile 2"
    assert (copied_profile / "Network" / "Cookies").exists()
    assert (copied_profile / "Network" / "Cookies-wal").exists()
    assert (copied_profile / "Preferences").exists()
    assert (copied_profile / "Secure Preferences").exists()
    assert not (copied_profile / "History").exists()
    assert not (copied_profile / "Cache").exists()
    assert (workspace / "Local State").exists()


def test_copy_profile_workspace_windows_error_points_to_shutdown_browser(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    user_data_dir = tmp_path / "User Data"
    profile_dir = user_data_dir / "Profile 11"
    cookies_db = profile_dir / "Network" / "Cookies"
    _write_cookies_db(cookies_db, reddit_rows=1)

    candidate = _candidate(
        user_data_dir=user_data_dir,
        profile_name="Profile 11",
        profile_dir=profile_dir,
        cookies_db=cookies_db,
    )

    real_copy2 = cookie_manager.shutil.copy2

    def fake_copy2(src, dst, *args, **kwargs):  # type: ignore[no-untyped-def]
        if Path(src) == cookies_db:
            raise PermissionError("locked")
        return real_copy2(src, dst, *args, **kwargs)

    monkeypatch.setattr(cookie_manager.shutil, "copy2", fake_copy2)
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")

    with pytest.raises(RuntimeError, match="--shutdown-browser"):
        cookie_manager._copy_profile_workspace(candidate, tmp_path / "workspace")


def test_cleanup_workspace_retries_transient_windows_lock(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    workspace = tmp_path / "workspace"
    workspace.mkdir()
    calls: list[Path] = []

    def fake_rmtree(path: Path) -> None:
        calls.append(Path(path))
        if len(calls) == 1:
            raise PermissionError("locked")

    monkeypatch.setattr(cookie_manager.shutil, "rmtree", fake_rmtree)
    monkeypatch.setattr(cookie_manager.time, "sleep", lambda _seconds: None)
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")

    cookie_manager._cleanup_workspace(workspace, silent=True)

    assert calls == [workspace, workspace]


def test_cleanup_workspace_warns_after_retries_instead_of_masking_result(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    workspace = tmp_path / "workspace"
    workspace.mkdir()
    messages: list[str] = []

    monkeypatch.setattr(cookie_manager.shutil, "rmtree", lambda _path: (_ for _ in ()).throw(PermissionError("locked")))
    monkeypatch.setattr(cookie_manager.time, "sleep", lambda _seconds: None)
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    monkeypatch.setattr(cookie_manager, "console_print", lambda message: messages.append(message))

    cookie_manager._cleanup_workspace(workspace, silent=False)

    assert any("temporary browser workspace" in message for message in messages)


def test_load_cookies_skips_unix_permission_fixups_on_windows(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    cookies_path = tmp_path / "cookies.json"
    cookies_path.write_text('{"reddit_session":"token"}', encoding="utf-8")
    messages: list[str] = []
    chmod_calls: list[int] = []

    monkeypatch.setattr(cookie_manager, "COOKIES_PATH", cookies_path)
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    monkeypatch.setattr(cookie_manager, "console_print", lambda message: messages.append(message))
    monkeypatch.setattr(Path, "chmod", lambda self, mode: chmod_calls.append(mode))

    cookies = cookie_manager.load_cookies()

    assert cookies == {"reddit_session": "token"}
    assert messages == []
    assert chmod_calls == []


def test_terminate_process_uses_taskkill_tree_on_windows(monkeypatch: pytest.MonkeyPatch) -> None:
    class FakeProcess:
        pid = 1234

        def __init__(self) -> None:
            self.terminated = False
            self.wait_calls = 0

        def poll(self) -> None:
            return None

        def terminate(self) -> None:
            self.terminated = True

        def wait(self, timeout: float | None = None) -> None:
            self.wait_calls += 1
            raise subprocess.TimeoutExpired(cmd="chrome", timeout=timeout or 0)

        def kill(self) -> None:
            raise AssertionError("kill() should not be used on Windows when taskkill succeeds")

    commands: list[list[str]] = []

    def fake_run(args, **kwargs):  # type: ignore[no-untyped-def]
        commands.append(list(args))

        class Result:
            returncode = 0

        return Result()

    process = FakeProcess()
    monkeypatch.setattr(cookie_manager.sys, "platform", "win32")
    monkeypatch.setattr(cookie_manager.subprocess, "run", fake_run)

    cookie_manager._terminate_process(process)

    assert process.terminated is True
    assert process.wait_calls == 1
    assert commands == [["taskkill", "/PID", "1234", "/T", "/F"]]
