from __future__ import annotations

import json
from collections.abc import Callable
from pathlib import Path
from unittest.mock import MagicMock

import pytest
from pygit2 import Commit, Oid, Repository, Signature

from pygittools.tasks import BOARD_REF_PREFIX, EMPTY_TREE_OID_HEX, TASK_REF_PREFIX, Board, Task
from pygittools.tui.branches import current_branch_name
from pygittools.tui.pages.branches import BranchPickerPage, branch_picker_page
from pygittools.tui.pages.help import HelpPage, help_page
from pygittools.tui.pages.home import HomePage, home_page
from pygittools.tui.pages.no_repo import NoRepoPage
from pygittools.tui.pages.projects import ProjectsPage, projects_page
from pygittools.tui.pages.task_editor import TaskEditorPage
from pygittools.tui.types import PageAction, PageContext
from pygittools.tui.ucurses import KEY_BACKSPACE, KEY_DOWN, KEY_ENTER, KEY_RESIZE, KEY_UP


def test_home_page_on_enter_and_quit(page_ctx: PageContext) -> None:
	page = home_page()
	page.on_enter(page_ctx)
	assert page.status_text()
	result = page.handle_key(ord("q"))
	assert result.action == PageAction.QUIT


def test_home_page_draw_and_tabs(committed_repo: Repository) -> None:
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = home_page()
	page.on_enter(ctx)
	page.draw(0, 20, 80)
	page.set_active_tab("log")
	page.draw(0, 20, 80)
	page.set_active_tab("tasks")
	page.draw(0, 20, 80)
	page.set_header_focus("repo")
	page.draw(0, 20, 80)
	page.handle_key(KEY_RESIZE)


def test_home_page_switch_to_input(page_ctx: PageContext) -> None:
	page = home_page()
	page.on_enter(page_ctx)
	page.handle_key(ord("a"))
	assert "Esc" in page.status_text()


def test_home_page_push_help(page_ctx: PageContext) -> None:
	page = home_page()
	page.on_enter(page_ctx)
	result = page.handle_key(ord("?"))
	assert result.action == PageAction.PUSH
	assert isinstance(result.next_page, HelpPage)


def test_home_page_draws_detached_head(committed_repo: Repository) -> None:
	commit = committed_repo.get(committed_repo.head.target)
	committed_repo.set_head(commit.id)
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = home_page()
	page.on_enter(ctx)
	page.draw(0, 20, 80)
	assert current_branch_name(committed_repo) == "(detached)"
	assert "j/k move" in page.status_text()
	page.set_active_tab("log")
	page.draw(0, 20, 80)
	assert "No commits" not in page.status_text()


def test_empty_repo_can_create_branch_and_commit(repo: Repository) -> None:
	assert repo.head_is_unborn
	assert list(repo.branches.local) == []
	ctx = PageContext(repo=repo, repo_path=Path(repo.path))
	page = home_page()
	assert isinstance(page, HomePage)
	page.on_enter(ctx)
	page.draw(0, 20, 80)
	page.set_active_tab("log")
	page.draw(0, 20, 80)
	assert page.status_text() == "No commits — j/k move"
	page.set_active_tab("status")
	page.draw(0, 20, 80)

	page.handle_key(KEY_UP)
	opened = page.handle_key(KEY_ENTER)
	assert opened.action == PageAction.PUSH
	assert isinstance(opened.next_page, BranchPickerPage)
	picker = opened.next_page
	picker.on_enter(ctx)
	picker.draw(0, 20, 80)
	for char in "feature":
		picker.handle_key(ord(char))
	created = picker.handle_key(KEY_ENTER)
	assert created.action == PageAction.POP
	assert current_branch_name(repo) == "feature"

	workdir = repo.workdir
	assert workdir is not None
	(Path(workdir) / "hello.txt").write_text("hello\n", encoding="utf-8")
	page.draw(0, 20, 80)
	page.handle_key(KEY_DOWN)
	_press_until(page, KEY_DOWN, lambda hint: hint.startswith("Enter stage —"))
	page.handle_key(KEY_ENTER)
	_press_until(page, KEY_UP, lambda hint: "type message" in hint.casefold())
	for char in "init":
		page.handle_key(ord(char))
	page.handle_key(KEY_ENTER)

	assert not repo.head_is_unborn, page.changes.status_hint()
	assert current_branch_name(repo) == "feature"
	assert "feature" in repo.branches.local
	assert repo.head.peel(Commit).message.splitlines()[0] == "init"
	page.set_active_tab("log")
	page.draw(0, 20, 80)
	assert "No commits" not in page.status_text()


def _press_until(page: HomePage, key: int, ready: Callable[[str], bool]) -> None:
	for _ in range(12):
		hint = page.status_text()
		if ready(hint):
			return
		page.handle_key(key)
	raise AssertionError(page.status_text())


def test_home_page_draw_without_context() -> None:
	page = home_page()
	page.draw(0, 10, 80)


def test_no_repo_page(tmp_path: Path) -> None:
	page = NoRepoPage(tmp_path)
	page.on_enter(PageContext(repo=MagicMock(), repo_path=tmp_path))  # type: ignore[arg-type]
	page.draw(0, 10, 80)
	assert page.handle_key(ord("q")).action == PageAction.QUIT
	assert page.handle_key(ord("h")).action == PageAction.PUSH
	page.handle_key(KEY_RESIZE)


def test_help_page_scroll_and_pop() -> None:
	page = help_page()
	page.on_enter(PageContext(repo=MagicMock(), repo_path=Path(".")))  # type: ignore[arg-type]
	page.draw(0, 5, 80)
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_UP)
	assert page.handle_key(ord("q")).action == PageAction.POP
	page.handle_key(KEY_RESIZE)


def test_branch_picker_lists_and_filters(committed_repo: Repository) -> None:
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = branch_picker_page()
	page.on_enter(ctx)
	page.draw(0, 10, 80)
	page.handle_key(ord("f"))
	page.handle_key(KEY_BACKSPACE)
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_UP)
	page.handle_key(KEY_ENTER)
	assert page.handle_key(ord("q")).action == PageAction.POP


def test_branch_picker_checkout_branch(committed_repo: Repository) -> None:
	commit = committed_repo.get(committed_repo.head.target)
	committed_repo.branches.create("feature", commit)
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = branch_picker_page()
	page.on_enter(ctx)
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_ENTER)
	assert page.handle_key(KEY_ENTER).action == PageAction.POP


def test_branch_picker_create_branch(committed_repo: Repository) -> None:
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = branch_picker_page()
	page.on_enter(ctx)
	page.handle_key(ord("n"))
	page.handle_key(ord("e"))
	page.handle_key(ord("w"))
	page.handle_key(KEY_ENTER)
	assert "new" in committed_repo.branches.local


def test_branch_picker_create_invalid_shows_error(committed_repo: Repository) -> None:
	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = branch_picker_page()
	page.on_enter(ctx)
	page.handle_key(ord(" "))
	page.handle_key(KEY_ENTER)
	assert page.status_text()


def test_branch_picker_draw_zero_height() -> None:
	page = BranchPickerPage()
	page.draw(0, 0, 80)


def test_projects_page_lists_repos(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
	root = tmp_path / "root"
	project = root / "demo"
	project.mkdir(parents=True)
	from pygit2 import init_repository

	repo = init_repository(str(project), bare=False)
	workdir = Path(repo.workdir)
	(workdir / "README").write_text("x\n", encoding="utf-8")
	repo.index.add("README")
	repo.index.write()
	tree = repo.index.write_tree()
	sig = Signature("t", "t@example.com")
	repo.create_commit("HEAD", sig, sig, "init", tree, [])
	settings = tmp_path / "settings.json"
	settings.write_text(json.dumps({"PROJECTROOT": str(root)}), encoding="utf-8")
	monkeypatch.setenv("PYGITWEB_SETTINGS_CONFIG", str(settings))
	ctx = PageContext(repo=Repository(str(project / ".git")), repo_path=project / ".git")
	page = projects_page()
	page.on_enter(ctx)
	page.draw(0, 10, 80)
	page.handle_key(KEY_DOWN)
	result = page.handle_key(KEY_ENTER)
	assert result.switch_repo == project.resolve()


def test_projects_page_empty(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
	settings = tmp_path / "settings.json"
	settings.write_text(json.dumps({"PROJECTROOT": str(tmp_path / "empty")}), encoding="utf-8")
	monkeypatch.setenv("PYGITWEB_SETTINGS_CONFIG", str(settings))
	page = projects_page()
	page.on_enter(PageContext(repo=MagicMock(), repo_path=tmp_path))  # type: ignore[arg-type]
	assert "No projects" in page.status_text()
	page.handle_key(KEY_ENTER)


def test_projects_page_draw_zero_height() -> None:
	ProjectsPage().draw(0, 0, 80)


def _empty_tree() -> Oid:
	return Oid(hex=EMPTY_TREE_OID_HEX)


def test_home_page_tasks_tab_creates_and_edits_tasks(committed_repo: Repository) -> None:
	board_ref = f"{BOARD_REF_PREFIX}Tasks"
	board = Board(_empty_tree(), board_ref, tagger="", description="Main")
	task = Task(
		_empty_tree(),
		f"{TASK_REF_PREFIX}alpha",
		"alice <alice@example.com>",
		title="Alpha",
		description="Do alpha",
		status=Task.Status.TODO,
	)
	task_oid = task.write(committed_repo)
	board.tasks = [str(task_oid)]
	board.update_message()
	board.write(committed_repo)

	ctx = PageContext(repo=committed_repo, repo_path=Path(committed_repo.path))
	page = home_page()
	page.on_enter(ctx)
	page.set_active_tab("tasks")
	page.draw(0, 20, 80)

	# Move to first status header and then to (+ New Task)
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_DOWN)
	result_new = page.handle_key(KEY_ENTER)
	assert result_new.action == PageAction.PUSH
	assert isinstance(result_new.next_page, TaskEditorPage)

	# Move to existing task row and open editor
	page = home_page()
	page.on_enter(ctx)
	page.set_active_tab("tasks")
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_DOWN)
	page.handle_key(KEY_DOWN)
	result_existing = page.handle_key(KEY_ENTER)
	assert result_existing.action == PageAction.PUSH
	assert isinstance(result_existing.next_page, TaskEditorPage)