from __future__ import annotations

from dataclasses import dataclass
from typing import Literal
from unittest.mock import MagicMock

import pytest
from pyratatui import Rect

from pygittools.tui.changes_list import ChangesList
from pygittools.tui.input_context import InputContext, command_context, input_context, nav_context
from pygittools.tui.keys import key, set_vim_enabled, vim_enabled
from pygittools.tui.log_list import CommitLogList
from pygittools.tui.pages.actions import ActionsPage
from pygittools.tui.pages.help import HelpPage
from pygittools.tui.tasks_list import TasksBoardList
from pygittools.tui.types import PageAction, PageContext
from pygittools.tui.worktrees_list import WorktreesList

_HeaderFocus = Literal["changes", "repo", "branch", "actions"]
_HomeTab = Literal["status", "log", "tasks", "worktrees"]


@dataclass
class FakeHost:
    page_context: PageContext | None
    changes: ChangesList
    log: CommitLogList
    tasks: TasksBoardList
    worktrees: WorktreesList
    header_focus: _HeaderFocus = "changes"
    active_tab: _HomeTab = "status"
    has_actions: bool = False

    def set_header_focus(self, focus: _HeaderFocus) -> None:
        self.header_focus = focus

    def set_active_tab(self, tab: _HomeTab) -> None:
        self.active_tab = tab


@pytest.fixture
def host(page_ctx: PageContext) -> FakeHost:
    set_vim_enabled(False)
    changes = ChangesList()
    changes.refresh(page_ctx.repo)
    log = CommitLogList()
    log.refresh(page_ctx.repo)
    tasks = TasksBoardList()
    tasks.refresh(page_ctx.repo)
    worktrees = WorktreesList()
    worktrees.refresh(page_ctx.repo)
    return FakeHost(page_context=page_ctx, changes=changes, log=log, tasks=tasks, worktrees=worktrees)


def test_nav_context_status_hints(host: FakeHost) -> None:
    nav = nav_context()
    host.header_focus = "repo"
    assert "browse projects" in nav.status_hint(host)
    host.header_focus = "branch"
    assert "switch branch" in nav.status_hint(host)
    host.active_tab = "log"
    host.header_focus = "changes"
    assert "arrows move" in nav.status_hint(host)


def test_nav_context_opens_command_mode(host: FakeHost) -> None:
    dispatch = nav_context().dispatch_key(host, key("/"))
    assert dispatch.next_mode == "command"


def _run_command(host: FakeHost, command: str):
    cmd = command_context()
    for char in command:
        cmd.dispatch_key(host, key(char))
    return cmd.dispatch_key(host, key("Enter"))


def test_command_opens_help(host: FakeHost) -> None:
    dispatch = _run_command(host, "help")
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.PUSH
    assert isinstance(dispatch.page_result.next_page, HelpPage)
    assert dispatch.next_mode == "nav"


def test_command_opens_actions(host: FakeHost) -> None:
    dispatch = _run_command(host, "actions")
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.PUSH
    assert isinstance(dispatch.page_result.next_page, ActionsPage)


def test_command_quit(host: FakeHost) -> None:
    dispatch = _run_command(host, "quit")
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.QUIT


def test_command_commit_enters_input(host: FakeHost) -> None:
    dispatch = _run_command(host, "commit")
    assert dispatch.next_mode == "input"
    assert host.active_tab == "status"
    assert host.header_focus == "changes"


def test_command_vim_toggle(host: FakeHost) -> None:
    assert not vim_enabled()
    assert _run_command(host, "vim").next_mode == "nav"
    assert vim_enabled()
    assert _run_command(host, "novim").next_mode == "nav"
    assert not vim_enabled()


def test_command_switch_tab(host: FakeHost) -> None:
    assert _run_command(host, "log").next_mode == "nav"
    assert host.active_tab == "log"


def test_command_unknown(host: FakeHost) -> None:
    cmd = command_context()
    for char in "nope":
        cmd.dispatch_key(host, key(char))
    dispatch = cmd.dispatch_key(host, key("Enter"))
    assert dispatch.next_mode is None
    assert "Unknown command" in cmd.status_hint(host)


def test_command_esc_cancels(host: FakeHost) -> None:
    cmd = command_context()
    cmd.dispatch_key(host, key("h"))
    dispatch = cmd.dispatch_key(host, key("Esc"))
    assert dispatch.next_mode == "nav"


def test_nav_context_repo_header(host: FakeHost) -> None:
    host.header_focus = "repo"
    nav = nav_context()
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "branch"
    dispatch = nav.dispatch_key(host, key("Enter"))
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.PUSH


def test_nav_context_branch_header(host: FakeHost) -> None:
    host.header_focus = "branch"
    nav = nav_context()
    nav.dispatch_key(host, key("Up"))
    assert host.header_focus == "repo"
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "branch"
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "actions"
    assert "No action groups" in nav.status_hint(host)
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "changes"
    host.header_focus = "branch"
    dispatch = nav.dispatch_key(host, key("Enter"))
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.PUSH


def test_nav_context_tab_switch(host: FakeHost) -> None:
    nav = nav_context()
    host.active_tab = "log"
    nav.dispatch_key(host, key("Left"))
    assert host.active_tab == "status"
    host.active_tab = "status"
    nav.dispatch_key(host, key("Right"))
    assert host.active_tab == "log"


def test_nav_context_actions_row(host: FakeHost) -> None:
    host.has_actions = True
    nav = nav_context()
    nav.dispatch_key(host, key("Up"))
    assert host.header_focus == "actions"
    assert "open actions" in nav.status_hint(host)
    nav.dispatch_key(host, key("Up"))
    assert host.header_focus == "branch"
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "actions"
    dispatch = nav.dispatch_key(host, key("Enter"))
    assert dispatch.page_result is not None
    assert dispatch.page_result.action == PageAction.PUSH
    assert isinstance(dispatch.page_result.next_page, ActionsPage)
    host.header_focus = "actions"
    nav.dispatch_key(host, key("Down"))
    assert host.header_focus == "changes"


def test_nav_context_moves_to_branch_from_top(host: FakeHost) -> None:
    host.changes._list_state.select(0)
    nav = nav_context()
    nav.dispatch_key(host, key("Up"))
    assert host.header_focus == "actions"
    assert nav.dispatch_key(host, key("Enter")).page_result is None
    nav.dispatch_key(host, key("Up"))
    assert host.header_focus == "branch"


def test_nav_context_log_tab(host: FakeHost) -> None:
    host.active_tab = "log"
    nav_context().dispatch_key(host, key("Down"))
    assert (host.log._list_state.selected or 0) >= 0


def test_nav_context_enters_input_on_enter(host: FakeHost) -> None:
    host.changes._list_state.select(0)
    dispatch = nav_context().dispatch_key(host, key("Enter"))
    assert dispatch.next_mode == "input"


def test_nav_context_vim_i_enters_input(host: FakeHost) -> None:
    set_vim_enabled(True)
    try:
        host.changes._list_state.select(0)
        dispatch = nav_context().dispatch_key(host, key("i"))
        assert dispatch.next_mode == "input"
    finally:
        set_vim_enabled(False)


def test_nav_ignores_letter_keys_without_vim(host: FakeHost) -> None:
    host.changes._list_state.select(0)
    selected = host.changes._list_state.selected
    dispatch = nav_context().dispatch_key(host, key("j"))
    assert dispatch.next_mode is None
    assert host.changes._list_state.selected == selected
    assert host.changes.commit_message == ""


def test_input_context_esc_returns_nav(host: FakeHost) -> None:
    dispatch = input_context().dispatch_key(host, key("Esc"))
    assert dispatch.next_mode == "nav"


def test_input_context_commit_relinquishes(host: FakeHost, monkeypatch: pytest.MonkeyPatch) -> None:
    host.changes.set_commit_message("msg")
    host.changes.begin_input()
    host.changes.refresh(host.page_context.repo)  # type: ignore[union-attr]
    monkeypatch.setattr(
        "pygittools.tui.changes_list.commit_staged",
        lambda _repo, _msg, *, amend=False: (True, ""),
    )
    input_context().dispatch_key(host, key("Tab"))
    dispatch = input_context().dispatch_key(host, key("Enter"))
    assert dispatch.next_mode == "nav"


def test_input_context_status_hint(host: FakeHost) -> None:
    host.changes.begin_input()
    assert "Esc" in InputContext().status_hint(host)


def test_nav_context_without_page_context() -> None:
    host = FakeHost(
        page_context=None,
        changes=ChangesList(),
        log=CommitLogList(),
        tasks=TasksBoardList(),
        worktrees=WorktreesList(),
    )
    assert nav_context().dispatch_key(host, key("Down")).page_result is None


def test_draw_helpers_unused() -> None:
    # keep import of Rect available for call sites in other modules
    assert Rect(0, 0, 1, 1).width == 1
    assert MagicMock() is not None