from __future__ import annotations

import shutil
from contextlib import suppress
from pathlib import Path

import pygit2

from pygitweb.config import settings

PYGITWEB_DIR = ".pygitweb"
WORKTREE_DIR = "wt"
WORKTREE_NAME = "pytesthtml"
REPORT_FILENAME = "pytest_report.html"


def project_root(project: str) -> Path:
	return (Path(settings.PROJECTROOT) / project).resolve()


def pygitweb_dir(project: str) -> Path:
	return project_root(project) / PYGITWEB_DIR


def report_path(project: str) -> Path:
	return pygitweb_dir(project) / REPORT_FILENAME


def _branch_reference(repo: pygit2.Repository) -> pygit2.Reference:
	try:
		return repo.lookup_reference(repo.head.name)
	except pygit2.GitError:
		pass
	for ref_name in repo.references:
		if ref_name.startswith("refs/heads/"):
			return repo.lookup_reference(ref_name)
	raise RuntimeError("repository has no branches to check out for pytest")


def _prune_worktree(repo: pygit2.Repository, name: str) -> None:
	with suppress(pygit2.GitError):
		repo.lookup_worktree(name).prune(True)


def _remove_untracked(wt_root: Path, wt_repo: pygit2.Repository) -> None:
	for rel_path, status in wt_repo.status().items():
		if status != pygit2.GIT_STATUS_WT_NEW:
			continue
		target = wt_root / rel_path
		if target.is_dir():
			shutil.rmtree(target)
		elif target.exists():
			target.unlink()


def _sync_worktree(wt_repo: pygit2.Repository, wt_root: Path, commit: pygit2.Commit) -> None:
	wt_repo.reset(commit.id, pygit2.GIT_RESET_HARD)
	_remove_untracked(wt_root, wt_repo)


def _ensure_bare_worktree(repo: pygit2.Repository, wt_path: Path) -> None:
	ref = _branch_reference(repo)
	commit = ref.peel(pygit2.Commit)
	wt_path.parent.mkdir(parents=True, exist_ok=True)
	path_str = str(wt_path.resolve())

	if WORKTREE_NAME in repo.list_worktrees():
		registered = Path(repo.lookup_worktree(WORKTREE_NAME).path)
		_sync_worktree(pygit2.Repository(str(registered)), registered, commit)
		return

	if wt_path.exists():
		shutil.rmtree(wt_path)

	try:
		repo.add_worktree(WORKTREE_NAME, path_str, ref)
	except pygit2.GitError:
		_prune_worktree(repo, WORKTREE_NAME)
		shutil.rmtree(wt_path, ignore_errors=True)
		repo.add_worktree(WORKTREE_NAME, path_str, ref)


def prepare_pytest_run(project: str) -> tuple[Path, Path]:
	"""Return (pytest cwd, html report path). Bare repos use a worktree under .pygitweb/wt."""
	repo_path = project_root(project)
	data_dir = pygitweb_dir(project)
	report = report_path(project)
	data_dir.mkdir(parents=True, exist_ok=True)

	repo = pygit2.Repository(str(repo_path))
	if not repo.is_bare:
		return repo_path, report

	wt_path = data_dir / WORKTREE_DIR
	_ensure_bare_worktree(repo, wt_path)
	return wt_path, report


def cleanup_pytest_run(project: str) -> None:
	"""Remove the bare-repo worktree after pytest; leaves .pygitweb/pytest_report.html."""
	repo_path = project_root(project)
	repo = pygit2.Repository(str(repo_path))
	if not repo.is_bare:
		return

	_prune_worktree(repo, WORKTREE_NAME)
	shutil.rmtree(pygitweb_dir(project) / WORKTREE_DIR, ignore_errors=True)
	shutil.rmtree(repo_path / "worktrees" / WORKTREE_NAME, ignore_errors=True)