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_stages_deleted_files(committed_repo: Repository) -> None:
    workdir = Path(committed_repo.workdir)
    (workdir / "tracked.txt").unlink()

    _, unstaged, _ = categorize_status(committed_repo)
    assert "tracked.txt" in unstaged

    stage_paths(committed_repo, unstaged)

    status = committed_repo.status()
    assert status["tracked.txt"] & FileStatus.INDEX_DELETED


def test_stage_path_stages_deleted_file(committed_repo: Repository) -> None:
    workdir = Path(committed_repo.workdir)
    (workdir / "tracked.txt").unlink()

    stage_path(committed_repo, "tracked.txt")

    status = committed_repo.status()
    assert status["tracked.txt"] & FileStatus.INDEX_DELETED


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_on_unborn_head(repo: Repository) -> None:
    path = Path(repo.workdir) / "hello.txt"
    path.write_text("hello\n", encoding="utf-8")
    stage_path(repo, "hello.txt")
    unstage_path(repo, "hello.txt")
    staged, _, untracked = categorize_status(repo)
    assert staged == []
    assert "hello.txt" in untracked


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_amend_success(committed_repo: Repository) -> None:
    ok, error = commit_staged(committed_repo, "initial\n\nbody", amend=True)
    assert ok is True
    assert error == ""


def test_commit_amend_requires_head(repo: Repository) -> None:
    ok, error = commit_staged(repo, "msg", amend=True)
    assert ok is False
    assert error == "Nothing to amend"


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"