from __future__ import annotations

from pathlib import Path

import pygit2
from pygit2 import Repository, Signature, init_repository

from pygittools.hooks import HookResult
from pygittools.hooks_push import PostCommitPushRemotes, force_with_lease_push

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


def _init_client_with_remote(tmp_path: Path) -> tuple[Repository, Path, Path, Path]:
    bare_path: Path = tmp_path / "remote.git"
    bare_path.mkdir()
    init_repository(str(bare_path), bare=True)
    backup_path: Path = tmp_path / "backup.git"
    backup_path.mkdir()
    init_repository(str(backup_path), bare=True)
    client_path: Path = tmp_path / "client"
    client: Repository = init_repository(str(client_path), bare=False)
    client.remotes.create("origin", str(bare_path))
    client.remotes.create("backup", str(backup_path))
    (client_path / "f").write_text("a\n", encoding="utf-8")
    index = client.index
    index.add("f")
    index.write()
    tree = index.write_tree()
    client.create_commit("HEAD", SIG, SIG, "init", tree, [])
    client.references.create("refs/heads/wip/topic", client.head.target, force=True)
    client.checkout("refs/heads/wip/topic")
    return client, client_path, bare_path, backup_path


def test_skips_non_matching_branch(tmp_path: Path) -> None:
    client, client_path, _bare, _backup = _init_client_with_remote(tmp_path)
    client.references.create("refs/heads/main", client.head.target, force=True)
    client.checkout("refs/heads/main")
    repo = pygit2.Repository(str(client_path))
    assert PostCommitPushRemotes(repo, r"^wip/").run() == HookResult.SUCCESS
    assert repo.references.get("refs/remotes/origin/main") is None


def test_pushes_matching_branch_to_all_remotes(tmp_path: Path) -> None:
    client, client_path, bare_path, backup_path = _init_client_with_remote(tmp_path)
    repo = pygit2.Repository(str(client_path))
    assert PostCommitPushRemotes(repo, r"^wip/").run() == HookResult.SUCCESS
    local_target = client.references["refs/heads/wip/topic"].target
    assert pygit2.Repository(str(bare_path)).references["refs/heads/wip/topic"].target == local_target
    assert pygit2.Repository(str(backup_path)).references["refs/heads/wip/topic"].target == local_target


def test_force_with_lease_rejects_stale_tracking_ref(tmp_path: Path) -> None:
    client, client_path, bare_path, _backup = _init_client_with_remote(tmp_path)
    repo = pygit2.Repository(str(client_path))
    assert force_with_lease_push(repo, "origin", "wip/topic") is None
    (client_path / "f").write_text("b\n", encoding="utf-8")
    index = repo.index
    index.add("f")
    index.write()
    tree = index.write_tree()
    parent = repo.head.target
    repo.create_commit("refs/heads/wip/topic", SIG, SIG, "second", tree, [parent])
    repo.checkout("refs/heads/wip/topic")
    bare = pygit2.Repository(str(bare_path))
    tb = bare.TreeBuilder()
    tree = tb.write()
    current = bare.references["refs/heads/wip/topic"].target
    other = bare.create_commit("refs/heads/wip/topic", SIG, SIG, "other", tree, [current])
    assert other != repo.references["refs/heads/wip/topic"].target
    error = force_with_lease_push(repo, "origin", "wip/topic")
    assert error is not None
    assert "rejected" in error.lower() or "failed" in error.lower()


def test_skips_detached_head(tmp_path: Path) -> None:
    client, client_path, _bare, _backup = _init_client_with_remote(tmp_path)
    client.set_head(client.head.target)
    repo = pygit2.Repository(str(client_path))
    assert repo.head_is_detached is True
    assert PostCommitPushRemotes(repo, r".*").run() == HookResult.SUCCESS