from pathlib import Path

import pytest
from pygit2 import GIT_OBJECT_BLOB, Oid, Repository, Signature, init_repository

from pygittools.merge import (
    MR_REF_PREFIX,
    MergeRequest,
    MergeRequestStatus,
    assert_merge_head_object,
    generate_mr_ref,
    get_merge_request,
    get_merge_request_by_oid,
    peel_mr_head_to_commit_oid,
)
from pygittools.tasks import EMPTY_TREE_OID_HEX, _ensure_empty_tree

SIG = Signature("alice", "alice@example.com")


@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 _commit(repo: Repository) -> Oid:
    tb = repo.TreeBuilder()
    tree = tb.write()
    return repo.create_commit("HEAD", SIG, SIG, "init", tree, [])


def _linear_ab(repo: Repository) -> tuple[Oid, Oid]:
    tb = repo.TreeBuilder()
    tree = tb.write()
    a = repo.create_commit(None, SIG, SIG, "a", tree, [])
    b = repo.create_commit(None, SIG, SIG, "b", tree, [a])
    repo.create_reference("refs/heads/main", a)
    return a, b


def test_generate_mr_ref_shape() -> None:
    ref = generate_mr_ref()
    assert ref.startswith(MR_REF_PREFIX)
    assert len(ref) == len(MR_REF_PREFIX) + 16


def test_mr_roundtrip_commit(repo: Repository) -> None:
    c = _commit(repo)
    ref = f"{MR_REF_PREFIX}deadbeefcafebabe"
    mr = MergeRequest(c, SIG, ours="refs/heads/main", title="Add feature", description="Details", name=ref)
    oid = mr.write(repo)

    loaded = get_merge_request(repo, ref)
    assert loaded.title == "Add feature"
    assert loaded.description == "Details"
    assert loaded.ours == "refs/heads/main"
    assert loaded.target == c
    assert loaded.name == ref

    loaded_oid = get_merge_request_by_oid(repo, oid)
    assert loaded_oid.title == "Add feature"


def test_mr_rejects_tree(repo: Repository) -> None:
    _ensure_empty_tree(repo)
    tree_oid = Oid(hex=EMPTY_TREE_OID_HEX)
    ref = f"{MR_REF_PREFIX}aaaaaaaaaaaaaaaa"
    mr = MergeRequest(tree_oid, SIG, ours="refs/heads/main", title="bad", name=ref)
    with pytest.raises(ValueError, match="tree"):
        mr.write(repo)


def test_mr_rejects_blob(repo: Repository) -> None:
    blob_oid = repo.odb.write(GIT_OBJECT_BLOB, b"hello")
    ref = f"{MR_REF_PREFIX}bbbbbbbbbbbbbbbb"
    mr = MergeRequest(blob_oid, SIG, ours="refs/heads/main", title="bad", name=ref)
    with pytest.raises(ValueError, match="blob"):
        mr.write(repo)


def test_mr_allows_tag_head(repo: Repository) -> None:
    c = _commit(repo)
    ref_base = f"{MR_REF_PREFIX}1111111111111111"
    mr1 = MergeRequest(c, SIG, ours="refs/heads/main", title="first", name=ref_base)
    tag_oid = mr1.write(repo)

    ref_second = f"{MR_REF_PREFIX}2222222222222222"
    mr2 = MergeRequest(tag_oid, SIG, ours="refs/heads/main", title="train", name=ref_second)
    mr2.write(repo)

    loaded = get_merge_request(repo, ref_second)
    assert loaded.title == "train"
    assert loaded.target == tag_oid


def test_assert_merge_head_object(repo: Repository) -> None:
    _ensure_empty_tree(repo)
    c = _commit(repo)
    assert assert_merge_head_object(repo, c) == "commit"
    tree_oid = Oid(hex=EMPTY_TREE_OID_HEX)
    with pytest.raises(ValueError, match="tree"):
        assert_merge_head_object(repo, tree_oid)


def test_merge_ff_advances_branch(repo: Repository) -> None:
    _a, b = _linear_ab(repo)
    mr_ref = f"{MR_REF_PREFIX}feedfeedfeedfeed"
    mr = MergeRequest(b, SIG, ours="refs/heads/main", title="ff", name=mr_ref)
    mr.write(repo)
    assert mr.check_status(repo) == MergeRequestStatus.CAN_FAST_FORWARD
    assert mr.merge(repo) == MergeRequestStatus.CAN_FAST_FORWARD
    assert repo.references["refs/heads/main"].resolve().target == b


def test_merge_diverged_blocked(repo: Repository) -> None:
    tb = repo.TreeBuilder()
    tree = tb.write()
    root = repo.create_commit(None, SIG, SIG, "root", tree, [])
    c1 = repo.create_commit(None, SIG, SIG, "c1", tree, [root])
    c2 = repo.create_commit(None, SIG, SIG, "c2", tree, [root])
    repo.create_reference("refs/heads/main", c1)
    mr_ref = f"{MR_REF_PREFIX}ddddeeeeffffffff"
    mr = MergeRequest(c2, SIG, ours="refs/heads/main", title="div", name=mr_ref)
    mr.write(repo)
    assert mr.check_status(repo) == MergeRequestStatus.BLOCKED_DIVERGED
    with pytest.raises(ValueError, match="fast-forward"):
        mr.merge(repo)
    assert repo.references["refs/heads/main"].resolve().target == c1


def test_merge_up_to_date_noop(repo: Repository) -> None:
    a, b = _linear_ab(repo)
    repo.references["refs/heads/main"].resolve().set_target(b)
    mr_ref = f"{MR_REF_PREFIX}aaaabbbbccccdddd"
    mr = MergeRequest(a, SIG, ours="refs/heads/main", title="behind", name=mr_ref)
    mr.write(repo)
    assert mr.check_status(repo) == MergeRequestStatus.UP_TO_DATE
    assert mr.merge(repo) == MergeRequestStatus.UP_TO_DATE
    assert repo.references["refs/heads/main"].resolve().target == b


def test_check_status_missing_branch(repo: Repository) -> None:
    c = _commit(repo)
    mr_ref = f"{MR_REF_PREFIX}eeeeffff00001111"
    mr = MergeRequest(c, SIG, ours="refs/heads/does-not-exist", title="x", name=mr_ref)
    mr.write(repo)
    assert mr.check_status(repo) == MergeRequestStatus.OURS_REF_MISSING


def test_refresh_from_theirs_advances_mr_head(repo: Repository) -> None:
    tb = repo.TreeBuilder()
    tree = tb.write()
    a = repo.create_commit(None, SIG, SIG, "a", tree, [])
    repo.create_reference("refs/heads/main", a)
    b = repo.create_commit(None, SIG, SIG, "b", tree, [a])
    repo.create_reference("refs/heads/feature", b)
    mr_ref = f"{MR_REF_PREFIX}0123456789abcdef"
    mr = MergeRequest(a, SIG, ours="refs/heads/main", title="track", name=mr_ref, theirs="refs/heads/feature")
    mr.write(repo)
    assert peel_mr_head_to_commit_oid(repo, mr.target) == a
    new_oid = mr.refresh_from_theirs(repo)
    assert new_oid is not None
    assert peel_mr_head_to_commit_oid(repo, mr.target) == b