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