import os
import subprocess
from pathlib import Path

import pytest
from pygit2 import Oid, Repository, init_repository

from pygittools.tasks import (
    ARCHIVE_BOARD_NAME,
    BOARD_REF_PREFIX,
    EMPTY_TREE_OID_HEX,
    TASK_REF_PREFIX,
    Board,
    Comment,
    Task,
    archive_task_local,
    delete_task_local,
    get_board,
    get_comment,
    get_task,
    get_task_by_oid,
)

TAGGER: str = "alice <alice@example.com>"
BOARD_MAIN: str = f"{BOARD_REF_PREFIX}main"


@pytest.fixture
def repo(tmp_path: Path) -> Repository:
    repo_path = tmp_path / "repo"
    repo_path.mkdir()

    repo = init_repository(str(repo_path), bare=False)

    # Set up initial commit on main to satisfy repo_head_or_empty()
    import subprocess

    os_repo_path = str(repo_path)
    subprocess.run(
        ["git", "config", "user.email", "test@test.com"],
        cwd=os_repo_path,
        check=True,
    )
    subprocess.run(
        ["git", "config", "user.name", "Test"],
        cwd=os_repo_path,
        check=True,
    )
    with open(os.path.join(os_repo_path, "README.md"), "w") as f:
        f.write("# initial\n")
    subprocess.run(["git", "add", "README.md"], cwd=os_repo_path, check=True)
    subprocess.run(
        ["git", "commit", "-m", "initial"],
        cwd=os_repo_path,
        check=True,
        capture_output=True,
        text=True,
    )

    return repo


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


def _task_ref(slug: str) -> str:
    return f"{TASK_REF_PREFIX}{slug}"


def _run_git_gc(repo: Repository) -> None:
    subprocess.run(
        ["git", "gc", "--prune=now"],
        cwd=repo.workdir,
        capture_output=True,
        text=True,
        check=True,
    )


def test_board_roundtrip(repo: Repository) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main board")
    board.write(repo)

    loaded = get_board(repo, BOARD_MAIN)
    assert loaded is not None
    assert loaded.name == BOARD_MAIN
    assert loaded.description == "Main board"
    assert loaded.tasks == []


def test_task_roundtrip_by_ref_and_oid(repo: Repository) -> None:
    task = Task(
        _empty_tree(),
        _task_ref("alpha"),
        TAGGER,
        title="Alpha",
        description="First task",
        status=Task.Status.TODO,
        priority=Task.Priority.HIGH,
        assignee="bob",
    )
    oid = task.write(repo)

    by_ref = get_task(repo, _task_ref("alpha"))
    assert by_ref is not None
    assert by_ref.title == "Alpha"
    assert by_ref.description == "First task"
    assert by_ref.status == Task.Status.TODO
    assert by_ref.priority == Task.Priority.HIGH
    assert by_ref.assignee == "bob"

    by_oid = get_task_by_oid(repo, oid)
    assert by_oid is not None
    assert by_oid.title == "Alpha"
    assert by_oid.status == Task.Status.TODO


def test_comment_roundtrip(repo: Repository) -> None:
    task = Task(_empty_tree(), _task_ref("with-comment"), TAGGER, title="WithComment")
    task_oid = task.write(repo)

    comment = Comment(task_oid, "bob <bob@example.com>", content="Hello there")
    c_oid = comment.write(repo)

    loaded = get_comment(repo, c_oid)
    assert loaded is not None
    assert loaded.content == "Hello there"
    assert loaded.target == task_oid


def test_full_board_flow(repo: Repository) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main")

    task_a = Task(
        _empty_tree(),
        _task_ref("a"),
        TAGGER,
        title="Task A",
        description="Desc A",
        status=Task.Status.TODO,
        priority=Task.Priority.HIGH,
    )
    task_a_oid = task_a.write(repo)

    task_b = Task(
        _empty_tree(),
        _task_ref("b"),
        TAGGER,
        title="Task B",
        description="Desc B",
        status=Task.Status.IN_PROGRESS,
        priority=Task.Priority.MEDIUM,
    )
    task_b_oid = task_b.write(repo)

    comment_a1 = Comment(task_a_oid, "bob <bob@example.com>", content="A1")
    a1_oid = comment_a1.write(repo)
    comment_a2 = Comment(task_a_oid, "carol <carol@example.com>", content="A2")
    a2_oid = comment_a2.write(repo)

    task_a.comments = [str(a1_oid), str(a2_oid)]
    task_a.update_message()
    task_a_oid = task_a.write(repo)

    comment_b1 = Comment(task_b_oid, "dave <dave@example.com>", content="B1")
    b1_oid = comment_b1.write(repo)

    task_b.comments = [str(b1_oid)]
    task_b.update_message()
    task_b_oid = task_b.write(repo)

    board.tasks = [str(task_a_oid), str(task_b_oid)]
    board.update_message()
    board.write(repo)

    loaded_board = get_board(repo, BOARD_MAIN)
    assert loaded_board is not None
    assert len(loaded_board.tasks) == 2

    loaded_a = get_task_by_oid(repo, Oid(hex=str(loaded_board.tasks[0])))
    assert loaded_a is not None
    assert loaded_a.title == "Task A"
    assert loaded_a.priority == Task.Priority.HIGH
    assert [get_comment(repo, Oid(hex=str(c))).content for c in loaded_a.comments] == ["A1", "A2"]

    loaded_b = get_task_by_oid(repo, Oid(hex=str(loaded_board.tasks[1])))
    assert loaded_b is not None
    assert loaded_b.title == "Task B"
    assert loaded_b.status == Task.Status.IN_PROGRESS
    assert [get_comment(repo, Oid(hex=str(c))).content for c in loaded_b.comments] == ["B1"]


def test_full_board_flow_survives_git_gc(repo: Repository, tmp_path: Path) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main")

    task = Task(
        _empty_tree(),
        _task_ref("gc-test"),
        TAGGER,
        title="GC Task",
        description="persists across gc",
        status=Task.Status.TODO,
        priority=Task.Priority.CRITICAL,
    )
    task_oid = task.write(repo)

    c1_oid = Comment(task_oid, "bob <bob@example.com>", content="first").write(repo)
    c2_oid = Comment(task_oid, "carol <carol@example.com>", content="second").write(repo)

    task.comments = [str(c1_oid), str(c2_oid)]
    task.update_message()
    task_oid = task.write(repo)

    board.tasks = [str(task_oid)]
    board.update_message()
    board.write(repo)

    _run_git_gc(repo)

    # Re-open repo to avoid any in-memory odb caches from the pre-gc instance.
    reopened = Repository(repo.path)

    loaded_board = get_board(reopened, BOARD_MAIN)
    assert loaded_board is not None
    assert len(loaded_board.tasks) == 1

    loaded_task = get_task_by_oid(reopened, Oid(hex=str(loaded_board.tasks[0])))
    assert loaded_task is not None
    assert loaded_task.title == "GC Task"
    assert loaded_task.priority == Task.Priority.CRITICAL
    assert len(loaded_task.comments) == 2

    contents = [get_comment(reopened, Oid(hex=str(c))).content for c in loaded_task.comments]
    assert contents == ["first", "second"]


def test_delete_task_local_removes_board_and_comment_refs(repo: Repository) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main")

    task = Task(
        _empty_tree(),
        _task_ref("to-delete"),
        TAGGER,
        title="To Delete",
        description="remove me",
        status=Task.Status.TODO,
    )
    task_oid = task.write(repo)

    comment = Comment(task_oid, "bob <bob@example.com>", content="bye")
    comment_oid = comment.write(repo)

    task.comments = [str(comment_oid)]
    task.update_message()
    task_oid = task.write(repo)

    board.tasks = [str(task_oid)]
    board.update_message()
    board.write(repo)

    comment_ref = get_comment(repo, comment_oid).name

    delete_task_local(repo, _task_ref("to-delete"))

    assert _task_ref("to-delete") not in repo.references
    assert comment_ref not in repo.references

    loaded_board = get_board(repo, BOARD_MAIN)
    assert loaded_board is not None
    assert loaded_board.tasks == []


def test_archive_task_local_moves_to_archive_board(repo: Repository) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main")
    task = Task(
        _empty_tree(),
        _task_ref("to-archive"),
        TAGGER,
        title="To Archive",
        description="move me",
        status=Task.Status.DONE,
    )
    task_oid = task.write(repo)
    board.tasks = [str(task_oid)]
    board.update_message()
    board.write(repo)

    archive_ref = archive_task_local(repo, _task_ref("to-archive"), "main")

    assert _task_ref("to-archive") in repo.references
    main_board = get_board(repo, BOARD_MAIN)
    assert main_board is not None
    assert main_board.tasks == []
    archive_board = get_board(repo, archive_ref)
    assert archive_board is not None
    assert archive_board.tasks == [str(task_oid)]
    assert archive_ref == f"{BOARD_REF_PREFIX}{ARCHIVE_BOARD_NAME}"


def test_create_task_returns_tuple(repo: Repository) -> None:
    """Shared create_task returns CreateTaskResult with task_id and oid fields"""
    from pygittools.tasks import create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Test board")
    result = create_task(repo, board, "Test Title")
    oide_hex = str(result["oid"])
    assert isinstance(result["id"], str)
    assert isinstance(oide_hex, str)
    assert len(oide_hex) == 40  # valid git SHA
    assert result["id"].startswith("task_")


def test_task_tag_persistence(repo: Repository) -> None:
    """Task tag created at refs/tags/tasks/{task_id} in repo ODB"""
    from pygittools.tasks import create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Tag test board")
    result = create_task(repo, board, "Tag Test")
    task_id = result["id"]
    ref = f"refs/tags/tasks/{task_id}"
    assert ref in repo.references


def test_board_updated(repo: Repository) -> None:
    """Board task list gains new OID after create_task"""
    from pygittools.tasks import create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Board test")
    original_len = len(board.tasks)
    result = create_task(repo, board, "Board Test")
    assert len(board.tasks) == original_len + 1
    assert result["oid"] in board.tasks


def test_default_parameters(repo: Repository) -> None:
    """Default params: TODO status, LOW priority"""
    from pygittools.tasks import Task, create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Defaults board")
    result = create_task(repo, board, "Defaults")
    task = result["task"]
    assert task.status == Task.Status.TODO
    assert task.priority == Task.Priority.LOW


def test_custom_parameters(repo: Repository) -> None:
    """Custom status, priority, assignee, due_date, tags, points"""
    from datetime import datetime

    from pygittools.tasks import Task, create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Custom board")
    result = create_task(
        repo,
        board,
        "Custom",
        status=Task.Status.IN_PROGRESS,
        priority=Task.Priority.HIGH,
        assignee="alice",
        due_date=datetime(2024, 1, 15),
        tags="urgent",
        points=5,
    )
    task = result["task"]
    assert task.assignee == "alice"
    assert task.due_date == datetime(2024, 1, 15)
    assert task.tags == "urgent"
    assert task.points == 5


def test_task_id_uniqueness(repo: Repository) -> None:
    """Running create_task twice creates distinct tasks (OIDs always unique)"""
    from pygittools.tasks import create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Uniqueness board")
    result1 = create_task(repo, board, "Task 1")
    result2 = create_task(repo, board, "Task 2")
    # OIDs are always unique since they're derived from Task.write()
    assert result1["oid"] != result2["oid"]
    # Both should persist on board
    assert str(result1["oid"]) in board.tasks
    assert str(result2["oid"]) in board.tasks


def test_board_message_updated(repo: Repository) -> None:
    """Board update_message and write are called as side effects"""
    from pygittools.tasks import create_task

    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Message test board")
    result = create_task(repo, board, "Message Test")
    # Verify the new OID appears on the board
    assert result["oid"] in board.tasks


def test_archive_task_local_creates_archive_board_when_missing(repo: Repository) -> None:
    board = Board(_empty_tree(), BOARD_MAIN, TAGGER, description="Main")
    task = Task(
        _empty_tree(),
        _task_ref("solo"),
        TAGGER,
        title="Solo",
        status=Task.Status.TODO,
    )
    task_oid = task.write(repo)
    board.tasks = [str(task_oid)]
    board.update_message()
    board.write(repo)

    archive_ref = archive_task_local(repo, _task_ref("solo"), "main")

    assert archive_ref in repo.references
    archive_board = get_board(repo, archive_ref)
    assert archive_board is not None
    assert getattr(archive_board, "description", "") == "Archived tasks"
    assert archive_board.tasks == [str(task_oid)]