"""Tests for brynq.consensus — democratic proposal and voting system."""

import time
import pytest
from brynq.consensus import ConsensusManager


@pytest.fixture
def cm(tmp_path):
    """Create a ConsensusManager with isolated storage."""
    return ConsensusManager(storage_dir=str(tmp_path / "consensus"))


class TestCreateProposal:
    def test_creates_proposal(self, cm):
        result = cm.create_proposal("Use FastAPI?", ["yes", "no"], created_by="agent-1")
        assert "proposal_id" in result
        assert result["quorum"] == 1

    def test_requires_two_options(self, cm):
        result = cm.create_proposal("Only one option", ["yes"])
        assert "error" in result

    def test_deduplicates_options(self, cm):
        result = cm.create_proposal("Dupes", ["a", "b", "a", "c"])
        p = cm.get_proposal(result["proposal_id"])
        assert p["options"] == ["a", "b", "c"]

    def test_custom_quorum(self, cm):
        result = cm.create_proposal("Vote", ["a", "b"], quorum=3)
        assert result["quorum"] == 3


class TestVoting:
    def test_cast_vote(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        result = cm.vote(pid, "agent-1", "a")
        assert result["ok"] is True
        assert result["total_votes"] == 1

    def test_change_vote(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        result = cm.vote(pid, "agent-1", "b")
        assert result["ok"] is True
        assert result["changed_from"] == "a"
        assert result["total_votes"] == 1  # Still 1 voter

    def test_invalid_option(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        result = cm.vote(pid, "agent-1", "c")
        assert result["ok"] is False

    def test_vote_nonexistent(self, cm):
        result = cm.vote("nonexistent", "agent-1", "a")
        assert result["ok"] is False

    def test_vote_on_resolved(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        cm.resolve(pid)
        result = cm.vote(pid, "agent-2", "b")
        assert result["ok"] is False


class TestRevokeVote:
    def test_revoke(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        result = cm.revoke_vote(pid, "agent-1")
        assert result["ok"] is True
        assert result["total_votes"] == 0

    def test_revoke_without_voting(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        result = cm.revoke_vote(pid, "agent-1")
        assert result["ok"] is False


class TestResolve:
    def test_clear_winner(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        cm.vote(pid, "agent-2", "a")
        cm.vote(pid, "agent-3", "b")
        result = cm.resolve(pid)
        assert result["winner"] == "a"
        assert result["quorum_met"] is True
        assert result["tally"]["a"] == 2
        assert result["tally"]["b"] == 1

    def test_no_votes(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        result = cm.resolve(pid)
        assert result["winner"] == ""
        assert result["quorum_met"] is False

    def test_quorum_not_met(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"], quorum=3)["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        result = cm.resolve(pid)
        assert result["quorum_met"] is False

    def test_tie_breaking(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        cm.vote(pid, "agent-2", "b")
        result = cm.resolve(pid)
        assert result["winner"] in ("a", "b")
        assert result.get("tie_broken") is True

    def test_already_resolved(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "agent-1", "a")
        cm.resolve(pid)
        result = cm.resolve(pid)
        assert result.get("already_resolved") is True


class TestCancel:
    def test_cancel_open(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        result = cm.cancel(pid, cancelled_by="admin")
        assert result["ok"] is True

    def test_cancel_resolved_fails(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"])["proposal_id"]
        cm.vote(pid, "a1", "a")
        cm.resolve(pid)
        result = cm.cancel(pid)
        assert result["ok"] is False


class TestQuery:
    def test_get_proposal(self, cm):
        pid = cm.create_proposal("Pick", ["a", "b"], created_by="me")["proposal_id"]
        cm.vote(pid, "a1", "a")
        p = cm.get_proposal(pid)
        assert p is not None
        assert p["description"] == "Pick"
        assert p["current_tally"]["a"] == 1

    def test_get_nonexistent(self, cm):
        assert cm.get_proposal("nonexistent") is None

    def test_list_open(self, cm):
        cm.create_proposal("P1", ["a", "b"], duration_sec=600)
        cm.create_proposal("P2", ["x", "y"], duration_sec=600)
        open_list = cm.list_open()
        assert len(open_list) == 2

    def test_list_resolved(self, cm):
        pid = cm.create_proposal("P1", ["a", "b"])["proposal_id"]
        cm.vote(pid, "a1", "a")
        cm.resolve(pid)
        resolved = cm.list_resolved()
        assert len(resolved) == 1
        assert resolved[0]["status"] == "resolved"
