"""Tests for Phase 73: Conflict Resolver (25+ tests)."""

import json
from pathlib import Path

import pytest

import brynq.server as srv
import brynq.conflict_resolver as cr


@pytest.fixture(autouse=True)
def isolate(tmp_path, monkeypatch):
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(cr, "CONFLICTS_DIR", tmp_path / "conflicts")
    (tmp_path / "conflicts").mkdir(parents=True, exist_ok=True)


# ── report_conflict ──────────────────────────────────────────────────────


class TestReportConflict:
    def test_basic_report(self):
        c = cr.report_conflict("src/app.py", "agent-a", "agent-b", "formatting war")
        assert c["file_path"] == "src/app.py"
        assert c["agent_a"] == "agent-a"
        assert c["agent_b"] == "agent-b"
        assert c["status"] == "open"
        assert "conflict_id" in c

    def test_unique_ids(self):
        c1 = cr.report_conflict("a.py", "x", "y", "d1")
        c2 = cr.report_conflict("b.py", "x", "y", "d2")
        assert c1["conflict_id"] != c2["conflict_id"]

    def test_persisted(self):
        c = cr.report_conflict("a.py", "x", "y", "desc")
        loaded = cr._load(c["conflict_id"])
        assert loaded is not None
        assert loaded["description"] == "desc"

    def test_history_logged(self):
        cr.report_conflict("a.py", "x", "y", "desc")
        events = srv._read_jsonl_sync(cr._history_path())
        assert len(events) == 1
        assert events[0]["event"] == "reported"


# ── get_conflicts ────────────────────────────────────────────────────────


class TestGetConflicts:
    def test_empty(self):
        assert cr.get_conflicts() == []

    def test_list_all(self):
        cr.report_conflict("a.py", "x", "y", "d1")
        cr.report_conflict("b.py", "x", "y", "d2")
        assert len(cr.get_conflicts()) == 2

    def test_filter_by_status(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.escalate_conflict(c["conflict_id"], "urgent")
        cr.report_conflict("b.py", "x", "y", "d2")

        assert len(cr.get_conflicts(status="open")) == 1
        assert len(cr.get_conflicts(status="escalated")) == 1

    def test_sorted_by_created_at(self):
        c1 = cr.report_conflict("a.py", "x", "y", "first")
        c2 = cr.report_conflict("b.py", "x", "y", "second")
        results = cr.get_conflicts()
        # Most recent first
        assert results[0]["conflict_id"] == c2["conflict_id"] or len(results) == 2


# ── propose_resolution ───────────────────────────────────────────────────


class TestProposeResolution:
    def test_basic_proposal(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        p = cr.propose_resolution(c["conflict_id"], "agent-a", "use my version")
        assert p["proposal"] == "use my version"
        assert p["index"] == 0

    def test_multiple_proposals(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan A")
        cr.propose_resolution(c["conflict_id"], "b", "plan B")
        loaded = cr._load(c["conflict_id"])
        assert len(loaded["proposals"]) == 2
        assert loaded["proposals"][1]["index"] == 1

    def test_proposal_nonexistent(self):
        result = cr.propose_resolution("nope", "a", "plan")
        assert "error" in result

    def test_proposal_on_resolved(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan A")
        cr.vote_on_resolution(c["conflict_id"], "v1", 0)
        cr.resolve_conflict(c["conflict_id"])
        result = cr.propose_resolution(c["conflict_id"], "b", "too late")
        assert "error" in result

    def test_proposal_history_logged(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan")
        events = srv._read_jsonl_sync(cr._history_path())
        proposal_events = [e for e in events if e["event"] == "proposal"]
        assert len(proposal_events) == 1


# ── vote_on_resolution ───────────────────────────────────────────────────


class TestVoteOnResolution:
    def test_basic_vote(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan A")
        result = cr.vote_on_resolution(c["conflict_id"], "voter1", 0)
        assert result["voted_for"] == 0

    def test_one_vote_per_agent(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "A")
        cr.propose_resolution(c["conflict_id"], "b", "B")
        cr.vote_on_resolution(c["conflict_id"], "voter1", 0)
        cr.vote_on_resolution(c["conflict_id"], "voter1", 1)  # change vote
        loaded = cr._load(c["conflict_id"])
        assert loaded["proposals"][0]["vote_count"] == 0
        assert loaded["proposals"][1]["vote_count"] == 1

    def test_invalid_proposal_index(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        result = cr.vote_on_resolution(c["conflict_id"], "v", 99)
        assert "error" in result

    def test_vote_nonexistent(self):
        result = cr.vote_on_resolution("nope", "v", 0)
        assert "error" in result

    def test_vote_on_escalated(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.escalate_conflict(c["conflict_id"])
        result = cr.vote_on_resolution(c["conflict_id"], "v", 0)
        assert "error" in result


# ── resolve_conflict ─────────────────────────────────────────────────────


class TestResolveConflict:
    def test_majority_wins(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan A")
        cr.propose_resolution(c["conflict_id"], "b", "plan B")
        cr.vote_on_resolution(c["conflict_id"], "v1", 0)
        cr.vote_on_resolution(c["conflict_id"], "v2", 0)
        cr.vote_on_resolution(c["conflict_id"], "v3", 1)

        result = cr.resolve_conflict(c["conflict_id"])
        assert result["status"] == "resolved"
        assert result["resolution"]["winning_proposal"] == 0
        assert result["resolution"]["votes"] == 2

    def test_tie_cannot_resolve(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "A")
        cr.propose_resolution(c["conflict_id"], "b", "B")
        cr.vote_on_resolution(c["conflict_id"], "v1", 0)
        cr.vote_on_resolution(c["conflict_id"], "v2", 1)

        result = cr.resolve_conflict(c["conflict_id"])
        assert "error" in result
        assert "Tie" in result["error"]

    def test_no_proposals(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        result = cr.resolve_conflict(c["conflict_id"])
        assert "error" in result

    def test_no_votes(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan")
        result = cr.resolve_conflict(c["conflict_id"])
        assert "error" in result

    def test_already_resolved(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan")
        cr.vote_on_resolution(c["conflict_id"], "v1", 0)
        cr.resolve_conflict(c["conflict_id"])
        result = cr.resolve_conflict(c["conflict_id"])
        assert "error" in result

    def test_nonexistent(self):
        result = cr.resolve_conflict("ghost")
        assert "error" in result


# ── escalate_conflict ────────────────────────────────────────────────────


class TestEscalateConflict:
    def test_escalate(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        result = cr.escalate_conflict(c["conflict_id"], "too complex")
        assert result["status"] == "escalated"
        assert result["escalation_reason"] == "too complex"

    def test_escalate_nonexistent(self):
        result = cr.escalate_conflict("ghost")
        assert "error" in result

    def test_cannot_escalate_resolved(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan")
        cr.vote_on_resolution(c["conflict_id"], "v", 0)
        cr.resolve_conflict(c["conflict_id"])
        result = cr.escalate_conflict(c["conflict_id"])
        assert "error" in result

    def test_escalation_history_logged(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.escalate_conflict(c["conflict_id"], "reason")
        events = srv._read_jsonl_sync(cr._history_path())
        esc = [e for e in events if e["event"] == "escalated"]
        assert len(esc) == 1


# ── get_conflict_stats ───────────────────────────────────────────────────


class TestGetConflictStats:
    def test_empty_stats(self):
        stats = cr.get_conflict_stats()
        assert stats["total"] == 0
        assert stats["open"] == 0
        assert stats["resolved"] == 0
        assert stats["escalated"] == 0

    def test_mixed_stats(self):
        c1 = cr.report_conflict("a.py", "x", "y", "d")
        c2 = cr.report_conflict("b.py", "x", "y", "d")
        c3 = cr.report_conflict("c.py", "x", "y", "d")

        cr.propose_resolution(c1["conflict_id"], "a", "plan")
        cr.vote_on_resolution(c1["conflict_id"], "v", 0)
        cr.resolve_conflict(c1["conflict_id"])
        cr.escalate_conflict(c2["conflict_id"])

        stats = cr.get_conflict_stats()
        assert stats["total"] == 3
        assert stats["resolved"] == 1
        assert stats["escalated"] == 1
        assert stats["open"] == 1

    def test_avg_resolution_time(self):
        c = cr.report_conflict("a.py", "x", "y", "d")
        cr.propose_resolution(c["conflict_id"], "a", "plan")
        cr.vote_on_resolution(c["conflict_id"], "v", 0)
        cr.resolve_conflict(c["conflict_id"])

        stats = cr.get_conflict_stats()
        assert stats["avg_resolution_seconds"] >= 0
