from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

from pygit2 import Repository, init_repository
from pyratatui import Rect

from pygittools.tui.app import _apply_result, _persist_commit_draft, run_tui
from pygittools.tui.keys import key
from pygittools.tui.pages.home import home_page
from pygittools.tui.pages.no_repo import NoRepoPage
from pygittools.tui.types import PageAction, PageContext, PageResult


def test_persist_commit_draft_skips_without_context() -> None:
    _persist_commit_draft([], None)


def test_persist_commit_draft_from_home_page(page_ctx: PageContext) -> None:
    page = home_page()
    page.on_enter(page_ctx)
    page.changes.set_commit_message("draft")
    _persist_commit_draft([page], page_ctx)
    from pygittools.tui.commit_draft import load_commit_draft

    assert load_commit_draft(page_ctx.repo_path, page_ctx.repo) == "draft"


def test_apply_result_replace(page_ctx: PageContext) -> None:
    next_page = home_page()
    stack = [NoRepoPage(Path("/tmp"))]
    ctx, new_stack = _apply_result(
        stack,
        page_ctx,
        PageResult(action=PageAction.REPLACE, next_page=next_page),
    )
    assert ctx is page_ctx
    assert new_stack == [next_page]


def test_apply_result_replace_without_page(page_ctx: PageContext) -> None:
    stack = [home_page()]
    ctx, new_stack = _apply_result(stack, page_ctx, PageResult(action=PageAction.REPLACE))
    assert new_stack is stack


def test_apply_result_push_and_pop(page_ctx: PageContext) -> None:
    home = home_page()
    home.on_enter(page_ctx)
    stack = [home]
    overlay = NoRepoPage(Path("/tmp"))
    ctx, stack = _apply_result(stack, page_ctx, PageResult(action=PageAction.PUSH, next_page=overlay))
    assert len(stack) == 2
    ctx, stack = _apply_result(stack, page_ctx, PageResult(action=PageAction.POP))
    assert len(stack) == 1


def test_apply_result_pop_repo_changed_refreshes_home(page_ctx: PageContext) -> None:
    home = home_page()
    home.on_enter(page_ctx)
    overlay = NoRepoPage(Path("/tmp"))
    with patch.object(home, "refresh") as refresh:
        ctx, stack = _apply_result(
            [home, overlay],
            page_ctx,
            PageResult(action=PageAction.POP, repo_changed=True),
        )
    assert ctx is page_ctx
    assert stack == [home]
    refresh.assert_called_once_with()


def test_apply_result_pop_without_repo_change_skips_refresh(page_ctx: PageContext) -> None:
    home = home_page()
    home.on_enter(page_ctx)
    overlay = NoRepoPage(Path("/tmp"))
    with patch.object(home, "refresh") as refresh:
        _apply_result([home, overlay], page_ctx, PageResult(action=PageAction.POP))
    refresh.assert_not_called()


def test_apply_result_pop_keeps_root(page_ctx: PageContext) -> None:
    home = home_page()
    ctx, stack = _apply_result([home], page_ctx, PageResult(action=PageAction.POP))
    assert stack == [home]


def test_apply_result_switch_repo(page_ctx: PageContext, tmp_path: Path) -> None:
    repo_dir = tmp_path / "other"
    repo_dir.mkdir()
    init_repository(str(repo_dir), bare=False)
    home = home_page()
    home.on_enter(page_ctx)
    ctx, stack = _apply_result(
        [home],
        page_ctx,
        PageResult(action=PageAction.PUSH, switch_repo=repo_dir),
    )
    assert ctx is not None
    assert stack[-1] is home


def test_apply_result_switch_repo_reenters_page(page_ctx: PageContext, tmp_path: Path) -> None:
    repo_dir = tmp_path / "other"
    repo_dir.mkdir()
    init_repository(str(repo_dir), bare=False)
    home = home_page()
    home.on_enter(page_ctx)
    ctx, stack = _apply_result(
        [home],
        page_ctx,
        PageResult(action=PageAction.NONE, switch_repo=repo_dir),
    )
    assert ctx is not None
    assert stack[-1] is home


class _FakeTerm:
    def __init__(self, keys: list[object]) -> None:
        self._keys = list(keys)
        self.hide_cursor = MagicMock()

    def __enter__(self) -> _FakeTerm:
        return self

    def __exit__(self, *args: object) -> bool:
        return False

    def draw(self, draw_fn: object) -> None:
        frame = MagicMock()
        frame.area = Rect(0, 0, 80, 24)
        draw_fn(frame)  # type: ignore[operator]

    def poll_event(self, timeout_ms: int = 0) -> object | None:
        del timeout_ms
        if not self._keys:
            return key("q")
        return self._keys.pop(0)


def _run_tui_once(repo_dir: Path, keys: list[object]) -> int:
    with (
        patch("pygittools.tui.app.Terminal", return_value=_FakeTerm(keys)),
        patch("pygittools.tui.app.init_tui_color_theme"),
        patch("pygittools.tui.app.init_tab_colors"),
    ):
        return run_tui(repo_dir)


def test_run_tui_quits_on_q(committed_repo: Repository) -> None:
    assert _run_tui_once(Path(committed_repo.workdir), [key("q")]) == 0


def test_run_tui_no_repo(tmp_path: Path) -> None:
    assert _run_tui_once(tmp_path, [key("q")]) == 0