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