from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock

import pytest
from pygit2 import Repository
from pyratatui import Rect

from pygittools.tui.changes_list import ChangesList, SectionId
from pygittools.tui.keys import key
from pygittools.tui.types import PageContext
from pygittools.tui.worktree import categorize_status


def test_refresh_builds_sections(committed_repo: Repository) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	assert changes.status_hint() != ""


def test_refresh_clean_repo(page_ctx: PageContext) -> None:
	changes = ChangesList()
	changes.refresh(page_ctx.repo)
	assert changes.status_hint() == "Enter type message — j/k move"


def test_nav_up_down(page_ctx: PageContext) -> None:
	changes = ChangesList()
	changes.refresh(page_ctx.repo)
	changes.handle_nav_key(key("Down"), page_ctx.repo)
	changes.handle_nav_key(key("Up"), page_ctx.repo)
	assert changes.is_at_top()


def test_stage_and_unstage_file(committed_repo: Repository) -> None:
	path = Path(committed_repo.workdir) / "tracked.txt"
	path.write_text("v2\n", encoding="utf-8")
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes._list_state.select(3)
	changes.handle_nav_key(key("Enter"), committed_repo)
	changes.refresh(committed_repo)
	staged, unstaged, _ = categorize_status(committed_repo)
	assert "tracked.txt" in staged or "tracked.txt" in unstaged


def test_toggle_section_stages_untracked(committed_repo: Repository) -> None:
	(Path(committed_repo.workdir) / "new.txt").write_text("x\n", encoding="utf-8")
	changes = ChangesList()
	changes.refresh(committed_repo)
	for index, row in enumerate(changes._rows):
		if row.kind == "header" and row.section == SectionId.UNTRACKED:
			changes._list_state.select(index)
			break
	result = changes.handle_nav_key(key("Enter"), committed_repo)
	assert result.layout_changed is True


def test_collapse_section_with_space(committed_repo: Repository) -> None:
	(Path(committed_repo.workdir) / "new.txt").write_text("x\n", encoding="utf-8")
	changes = ChangesList()
	changes.refresh(committed_repo)
	for index, row in enumerate(changes._rows):
		if row.kind == "header" and row.section == SectionId.UNSTAGED:
			changes._list_state.select(index)
			break
	result = changes.handle_nav_key(key(" "), committed_repo)
	assert result.layout_changed is True


def test_commit_input_flow(committed_repo: Repository, monkeypatch: pytest.MonkeyPatch) -> None:
	(Path(committed_repo.workdir) / "new.txt").write_text("x\n", encoding="utf-8")
	committed_repo.index.add("new.txt")
	committed_repo.index.write()
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes._list_state.select(0)
	changes.handle_nav_key(key("f"), committed_repo)
	assert changes.commit_message.endswith("f")
	changes.handle_input_key(key("Backspace"), committed_repo)
	result = changes.handle_input_key(key("Enter"), committed_repo)
	# may fail without staged depending on index state; exercise path
	assert result is not None


def test_commit_enter_with_message(committed_repo: Repository, monkeypatch: pytest.MonkeyPatch) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes.set_commit_message("msg")
	monkeypatch.setattr(
		"pygittools.tui.changes_list.commit_staged",
		lambda _repo, _msg: (True, ""),
	)
	result = changes.handle_nav_key(key("Enter"), committed_repo)
	assert result.layout_changed is True


def test_commit_enter_failure(committed_repo: Repository, monkeypatch: pytest.MonkeyPatch) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes.set_commit_message("msg")
	monkeypatch.setattr(
		"pygittools.tui.changes_list.commit_staged",
		lambda _repo, _msg: (False, "nope"),
	)
	changes.handle_nav_key(key("Enter"), committed_repo)
	assert "nope" in changes.status_hint()


def test_enter_empty_commit_starts_input(committed_repo: Repository) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes._list_state.select(0)
	result = changes.handle_nav_key(key("Enter"), committed_repo)
	assert result.enter_input is True


def test_stage_file_hint(committed_repo: Repository) -> None:
	path = Path(committed_repo.workdir) / "tracked.txt"
	path.write_text("v2\n", encoding="utf-8")
	changes = ChangesList()
	changes.refresh(committed_repo)
	for index, row in enumerate(changes._rows):
		if row.kind == "file" and row.section == SectionId.UNSTAGED:
			changes._list_state.select(index)
			break
	assert "Enter stage" in changes.status_hint()


def test_draw_renders(committed_repo: Repository) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	frame = MagicMock()
	area = Rect(0, 0, 80, 20)
	changes.draw(frame, area, highlight=True, commit_input=True)
	changes.draw(frame, area, highlight=True, commit_input=False)


def test_draw_commit_input_does_not_highlight_shifted_row(committed_repo: Repository) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	changes._list_state.select(0)
	frame = MagicMock()
	changes.draw(frame, Rect(0, 0, 80, 20), highlight=True, commit_input=True)
	assert frame.render_stateful_list.called
	list_state = frame.render_stateful_list.call_args.args[2]
	assert list_state.selected is None
	assert (changes._list_state.selected or 0) == 0


def test_persist_commit_draft(page_ctx: PageContext, tmp_path: Path) -> None:
	changes = ChangesList()
	changes.set_commit_message("draft")
	changes.persist_commit_draft(page_ctx.repo)


def test_toggle_staged_header_when_empty(committed_repo: Repository) -> None:
	changes = ChangesList()
	changes.refresh(committed_repo)
	for index, row in enumerate(changes._rows):
		if row.kind == "header" and row.section == SectionId.STAGED:
			changes._list_state.select(index)
			break
	result = changes.handle_nav_key(key("Enter"), committed_repo)
	assert result.layout_changed is True