from __future__ import annotations

import subprocess
from pathlib import Path
from unittest.mock import MagicMock

import pytest
from pygit2 import Repository, init_repository
from pygit2.enums import FileStatus

from pygittools.tui.worktree import (
	categorize_status,
	commit_staged,
	stage_path,
	stage_paths,
	unstage_path,
	unstage_paths,
)


@pytest.fixture
def repo(tmp_path: Path) -> Repository:
	repo_path = tmp_path / "repo"
	repo_path.mkdir()
	return init_repository(str(repo_path), bare=False)


def _init_nested_repo(path: Path) -> None:
	path.mkdir()
	subprocess.run(["git", "init"], cwd=path, check=True, capture_output=True)
	(path / "hello.txt").write_text("hello\n", encoding="utf-8")
	subprocess.run(["git", "-C", str(path), "add", "hello.txt"], check=True, capture_output=True)
	subprocess.run(
		[
			"git",
			"-C",
			str(path),
			"-c",
			"user.email=alice@example.com",
			"-c",
			"user.name=alice",
			"commit",
			"-m",
			"init",
		],
		check=True,
		capture_output=True,
	)


def test_stage_path_accepts_untracked_directory_trailing_slash(repo: Repository) -> None:
	workdir = Path(repo.workdir)
	_init_nested_repo(workdir / "nested")

	_, _, untracked = categorize_status(repo)
	assert untracked == ["nested/"]

	stage_path(repo, untracked[0])

	staged, _, remaining = categorize_status(repo)
	assert staged == ["nested"]
	assert remaining == []


def test_stage_paths_accepts_untracked_directory_trailing_slash(repo: Repository) -> None:
	workdir = Path(repo.workdir)
	_init_nested_repo(workdir / "pkg")
	(workdir / "loose.txt").write_text("x\n", encoding="utf-8")

	_, _, untracked = categorize_status(repo)
	assert set(untracked) == {"pkg/", "loose.txt"}

	stage_paths(repo, untracked)

	status = repo.status()
	assert status["pkg"] & FileStatus.INDEX_NEW
	assert status["loose.txt"] & FileStatus.INDEX_NEW


def test_stage_paths_noop_for_empty_list(repo: Repository) -> None:
	stage_paths(repo, [])


def test_unstage_tracked_file(committed_repo: Repository) -> None:
	stage_path(committed_repo, "tracked.txt")
	unstage_path(committed_repo, "tracked.txt")
	staged, _, _ = categorize_status(committed_repo)
	assert "tracked.txt" not in staged


def test_unstage_new_file_removes_from_index(committed_repo: Repository) -> None:
	path = Path(committed_repo.workdir) / "new.txt"
	path.write_text("x\n", encoding="utf-8")
	stage_path(committed_repo, "new.txt")
	unstage_path(committed_repo, "new.txt")
	staged, _, untracked = categorize_status(committed_repo)
	assert "new.txt" not in staged
	assert "new.txt" in untracked


def test_unstage_paths_batch(committed_repo: Repository) -> None:
	path = Path(committed_repo.workdir) / "new.txt"
	path.write_text("x\n", encoding="utf-8")
	stage_path(committed_repo, "new.txt")
	unstage_paths(committed_repo, ["new.txt", "tracked.txt"])
	staged, unstaged, _ = categorize_status(committed_repo)
	assert not staged


def test_commit_staged_validates_message(committed_repo: Repository) -> None:
	ok, error = commit_staged(committed_repo, "   ")
	assert ok is False
	assert error == "Commit message is empty"


def test_commit_staged_requires_staged_files(committed_repo: Repository) -> None:
	ok, error = commit_staged(committed_repo, "msg")
	assert ok is False
	assert error == "Nothing staged to commit"


def test_commit_staged_success(committed_repo: Repository) -> None:
	path = Path(committed_repo.workdir) / "new.txt"
	path.write_text("x\n", encoding="utf-8")
	stage_path(committed_repo, "new.txt")
	ok, error = commit_staged(committed_repo, "add file")
	assert ok is True
	assert error == ""


def test_commit_staged_surfaces_git_error(committed_repo: Repository, monkeypatch: pytest.MonkeyPatch) -> None:
	path = Path(committed_repo.workdir) / "new.txt"
	path.write_text("x\n", encoding="utf-8")
	stage_path(committed_repo, "new.txt")
	result = MagicMock(returncode=1, stderr="", stdout="")
	monkeypatch.setattr("pygittools.tui.worktree.subprocess.run", lambda *a, **k: result)
	ok, error = commit_staged(committed_repo, "msg")
	assert ok is False
	assert error == "Commit failed"