"""
Unit tests for the voting/consensus system.
"""

import pytest
from datetime import datetime, timedelta
from pathlib import Path

from agent_orchestrator.persistence.database import OrchestratorDB
from agent_orchestrator.persistence.models import (
    Agent,
    VotingSession,
    Vote,
    QuorumType,
)
from agent_orchestrator.consensus.voting import (
    VotingCoordinator,
    VotingResult,
    TieBreaker,
)


class TestVotingSession:
    """Tests for VotingSession model."""

    def test_voting_session_is_open(self):
        """Test is_open property."""
        session = VotingSession(
            id="vote-1",
            topic="Test topic",
            created_by="orchestrator",
            status="open",
        )
        assert session.is_open is True

        session.status = "closed"
        assert session.is_open is False


class TestVote:
    """Tests for Vote model."""

    def test_vote_creation(self):
        """Test creating a vote."""
        vote = Vote(
            session_id="vote-1",
            agent_id="agent-1",
            choice="option_a",
            confidence=0.8,
            rationale="Seems like the best choice",
        )
        assert vote.session_id == "vote-1"
        assert vote.agent_id == "agent-1"
        assert vote.choice == "option_a"
        assert vote.confidence == 0.8


class TestVotingDatabase:
    """Tests for voting database operations."""

    @pytest.fixture
    def db(self, temp_db_path: Path) -> OrchestratorDB:
        """Create a test database."""
        return OrchestratorDB(temp_db_path)

    def test_create_voting_session(self, db: OrchestratorDB):
        """Test creating a voting session."""
        session = VotingSession(
            id="vote-1",
            topic="Which framework to use?",
            description="Choose between React and Vue",
            created_by="orchestrator",
            required_voters=3,
            quorum_type="simple_majority",
        )
        session_id = db.create_voting_session(session)

        assert session_id == "vote-1"

        retrieved = db.get_voting_session("vote-1")
        assert retrieved is not None
        assert retrieved.topic == "Which framework to use?"
        assert retrieved.required_voters == 3

    def test_cast_and_get_vote(self, db: OrchestratorDB):
        """Test casting and retrieving votes."""
        # Create session
        db.create_voting_session(VotingSession(
            id="vote-1",
            topic="Test vote",
            created_by="orchestrator",
        ))

        # Create agents
        db.create_agent(Agent(id="agent-1", tool="claude_code"))
        db.create_agent(Agent(id="agent-2", tool="claude_code"))

        # Cast votes
        db.cast_vote(Vote(
            session_id="vote-1",
            agent_id="agent-1",
            choice="option_a",
            confidence=0.9,
        ))
        db.cast_vote(Vote(
            session_id="vote-1",
            agent_id="agent-2",
            choice="option_b",
            confidence=0.7,
        ))

        # Get votes
        votes = db.get_votes("vote-1")
        assert len(votes) == 2

        # Get vote counts
        counts = db.get_vote_counts("vote-1")
        assert counts["option_a"] == 1
        assert counts["option_b"] == 1

    def test_weighted_vote_counts(self, db: OrchestratorDB):
        """Test confidence-weighted vote counts."""
        db.create_voting_session(VotingSession(
            id="vote-1",
            topic="Test",
            created_by="orchestrator",
        ))

        db.create_agent(Agent(id="agent-1", tool="claude_code"))
        db.create_agent(Agent(id="agent-2", tool="claude_code"))
        db.create_agent(Agent(id="agent-3", tool="claude_code"))

        # Agent 1 votes option_a with high confidence
        db.cast_vote(Vote(session_id="vote-1", agent_id="agent-1",
                          choice="option_a", confidence=1.0))
        # Agent 2 votes option_b with medium confidence
        db.cast_vote(Vote(session_id="vote-1", agent_id="agent-2",
                          choice="option_b", confidence=0.5))
        # Agent 3 votes option_b with low confidence
        db.cast_vote(Vote(session_id="vote-1", agent_id="agent-3",
                          choice="option_b", confidence=0.3))

        weighted = db.get_weighted_vote_counts("vote-1")
        assert weighted["option_a"] == 1.0
        assert weighted["option_b"] == pytest.approx(0.8)

    def test_has_agent_voted(self, db: OrchestratorDB):
        """Test checking if agent has voted."""
        db.create_voting_session(VotingSession(
            id="vote-1",
            topic="Test",
            created_by="orchestrator",
        ))
        db.create_agent(Agent(id="agent-1", tool="claude_code"))

        # Not voted yet
        assert db.has_agent_voted("vote-1", "agent-1") is False

        # Cast vote
        db.cast_vote(Vote(session_id="vote-1", agent_id="agent-1", choice="a"))

        # Now voted
        assert db.has_agent_voted("vote-1", "agent-1") is True

    def test_close_voting_session(self, db: OrchestratorDB):
        """Test closing a voting session."""
        db.create_voting_session(VotingSession(
            id="vote-1",
            topic="Test",
            created_by="orchestrator",
        ))

        db.close_voting_session("vote-1", "option_a", "Option A won with majority")

        session = db.get_voting_session("vote-1")
        assert session.status == "closed"
        assert session.winning_option == "option_a"
        assert session.closed_at is not None

    def test_get_open_voting_sessions(self, db: OrchestratorDB):
        """Test getting open voting sessions."""
        db.create_voting_session(VotingSession(
            id="vote-1",
            topic="Open vote",
            created_by="orchestrator",
            status="open",
        ))
        db.create_voting_session(VotingSession(
            id="vote-2",
            topic="Closed vote",
            created_by="orchestrator",
            status="closed",
        ))

        open_sessions = db.get_open_voting_sessions()
        assert len(open_sessions) == 1
        assert open_sessions[0].id == "vote-1"


class TestVotingCoordinator:
    """Tests for VotingCoordinator class."""

    @pytest.fixture
    def db(self, temp_db_path: Path) -> OrchestratorDB:
        """Create a test database with agents."""
        db = OrchestratorDB(temp_db_path)
        # Create test agents
        db.create_agent(Agent(id="agent-1", tool="claude_code"))
        db.create_agent(Agent(id="agent-2", tool="claude_code"))
        db.create_agent(Agent(id="agent-3", tool="claude_code"))
        db.create_agent(Agent(id="agent-4", tool="claude_code"))
        db.create_agent(Agent(id="agent-5", tool="claude_code"))
        return db

    @pytest.fixture
    def coordinator(self, db: OrchestratorDB) -> VotingCoordinator:
        """Create a voting coordinator."""
        return VotingCoordinator(
            db=db,
            default_quorum=3,
            default_quorum_type=QuorumType.SIMPLE_MAJORITY,
        )

    def test_create_session(self, coordinator: VotingCoordinator):
        """Test creating a voting session."""
        session = coordinator.create_session(
            topic="Choose a framework",
            created_by="orchestrator",
            options=["React", "Vue", "Angular"],
            description="Which frontend framework should we use?",
        )

        assert session.id is not None
        assert session.topic == "Choose a framework"
        assert session.required_voters == 3
        assert session.is_open is True
        assert "React" in session.description

    def test_cast_vote(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test casting a vote."""
        session = coordinator.create_session(
            topic="Test vote",
            created_by="orchestrator",
            options=["a", "b"],
        )

        result = coordinator.cast_vote(
            session_id=session.id,
            agent_id="agent-1",
            choice="a",
            confidence=0.9,
            rationale="A is better",
        )

        assert result is True
        assert db.has_agent_voted(session.id, "agent-1") is True

    def test_simple_majority_vote(self, coordinator: VotingCoordinator):
        """Test simple majority voting."""
        session = coordinator.create_session(
            topic="Test majority",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=3,
            quorum_type=QuorumType.SIMPLE_MAJORITY,
        )

        # 2 votes for a, 1 for b
        coordinator.cast_vote(session.id, "agent-1", "a")
        coordinator.cast_vote(session.id, "agent-2", "a")
        coordinator.cast_vote(session.id, "agent-3", "b")

        result = coordinator.tally_votes(session.id)

        assert result.has_winner is True
        assert result.winner == "a"
        assert result.quorum_met is True
        assert result.threshold_met is True
        assert result.vote_counts["a"] == 2
        assert result.vote_counts["b"] == 1

    def test_quorum_not_met(self, coordinator: VotingCoordinator):
        """Test when quorum is not met."""
        session = coordinator.create_session(
            topic="Test quorum",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=3,
        )

        # Only 2 votes
        coordinator.cast_vote(session.id, "agent-1", "a")
        coordinator.cast_vote(session.id, "agent-2", "a")

        result = coordinator.tally_votes(session.id)

        assert result.quorum_met is False
        assert result.has_winner is False

    def test_supermajority_vote(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test supermajority voting (2/3 required)."""
        coord = VotingCoordinator(
            db=db,
            default_quorum=3,
            default_quorum_type=QuorumType.SUPERMAJORITY,
        )

        session = coord.create_session(
            topic="Test supermajority",
            created_by="orchestrator",
            options=["a", "b"],
            quorum_type=QuorumType.SUPERMAJORITY,
            supermajority_threshold=0.67,
        )

        # 2 votes for a, 1 for b - doesn't meet 67% threshold
        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "a")
        coord.cast_vote(session.id, "agent-3", "b")

        result = coord.tally_votes(session.id)

        # 2/3 = 66.7% which is just under 67%
        assert result.quorum_met is True
        assert result.threshold_met is False
        assert result.has_winner is False

    def test_supermajority_passes(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test supermajority passes with enough votes."""
        coord = VotingCoordinator(db=db, default_quorum=3)

        session = coord.create_session(
            topic="Test supermajority pass",
            created_by="orchestrator",
            options=["a", "b"],
            quorum_type=QuorumType.SUPERMAJORITY,
            supermajority_threshold=0.67,
        )

        # 3 votes for a, 1 for b - 75% meets 67% threshold
        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "a")
        coord.cast_vote(session.id, "agent-3", "a")
        coord.cast_vote(session.id, "agent-4", "b")

        result = coord.tally_votes(session.id)

        assert result.quorum_met is True
        assert result.threshold_met is True
        assert result.winner == "a"

    def test_unanimous_vote(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test unanimous voting."""
        coord = VotingCoordinator(db=db, default_quorum=3)

        session = coord.create_session(
            topic="Test unanimous",
            created_by="orchestrator",
            options=["a", "b"],
            quorum_type=QuorumType.UNANIMOUS,
        )

        # All vote for a
        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "a")
        coord.cast_vote(session.id, "agent-3", "a")

        result = coord.tally_votes(session.id)

        assert result.quorum_met is True
        assert result.threshold_met is True
        assert result.winner == "a"

    def test_unanimous_fails(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test unanimous voting fails with any dissent."""
        coord = VotingCoordinator(db=db, default_quorum=3)

        session = coord.create_session(
            topic="Test unanimous fail",
            created_by="orchestrator",
            options=["a", "b"],
            quorum_type=QuorumType.UNANIMOUS,
        )

        # 2 for a, 1 for b
        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "a")
        coord.cast_vote(session.id, "agent-3", "b")

        result = coord.tally_votes(session.id)

        assert result.quorum_met is True
        assert result.threshold_met is False
        assert result.has_winner is False

    def test_tie_breaker_highest_confidence(
        self, coordinator: VotingCoordinator, db: OrchestratorDB
    ):
        """Test tie breaking by highest confidence."""
        coord = VotingCoordinator(
            db=db,
            default_quorum=2,
            default_tie_breaker=TieBreaker.HIGHEST_CONFIDENCE,
        )

        session = coord.create_session(
            topic="Test tie",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=2,
        )

        # Tie in votes, but higher confidence for 'b'
        coord.cast_vote(session.id, "agent-1", "a", confidence=0.5)
        coord.cast_vote(session.id, "agent-2", "b", confidence=0.9)

        result = coord.tally_votes(session.id, tie_breaker=TieBreaker.HIGHEST_CONFIDENCE)

        assert result.tie_broken is True
        assert result.winner == "b"
        assert result.tie_breaker_used == "highest_confidence"

    def test_tie_breaker_first_vote(
        self, coordinator: VotingCoordinator, db: OrchestratorDB
    ):
        """Test tie breaking by first vote."""
        coord = VotingCoordinator(db=db, default_quorum=2)

        session = coord.create_session(
            topic="Test tie first",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=2,
        )

        # First vote is for 'a'
        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "b")

        result = coord.tally_votes(session.id, tie_breaker=TieBreaker.FIRST_VOTE)

        assert result.tie_broken is True
        assert result.winner == "a"
        assert result.tie_breaker_used == "first_vote"

    def test_tie_breaker_no_decision(
        self, coordinator: VotingCoordinator, db: OrchestratorDB
    ):
        """Test tie with no decision."""
        coord = VotingCoordinator(db=db, default_quorum=2)

        session = coord.create_session(
            topic="Test no decision",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=2,
        )

        coord.cast_vote(session.id, "agent-1", "a")
        coord.cast_vote(session.id, "agent-2", "b")

        result = coord.tally_votes(session.id, tie_breaker=TieBreaker.NO_DECISION)

        assert result.tie_broken is True
        assert result.winner is None
        assert result.has_winner is False

    def test_get_pending_votes_for_agent(self, coordinator: VotingCoordinator):
        """Test getting pending votes for an agent."""
        session1 = coordinator.create_session(
            topic="Vote 1",
            created_by="orchestrator",
            options=["a", "b"],
        )
        session2 = coordinator.create_session(
            topic="Vote 2",
            created_by="orchestrator",
            options=["x", "y"],
        )

        # Agent 1 votes in session 1
        coordinator.cast_vote(session1.id, "agent-1", "a")

        # Agent 1 should only see session 2 as pending
        pending = coordinator.get_pending_votes_for_agent("agent-1")
        assert len(pending) == 1
        assert pending[0].id == session2.id

        # Agent 2 should see both as pending
        pending = coordinator.get_pending_votes_for_agent("agent-2")
        assert len(pending) == 2

    def test_session_closes_after_tally(self, coordinator: VotingCoordinator, db: OrchestratorDB):
        """Test that session closes after tallying."""
        session = coordinator.create_session(
            topic="Test close",
            created_by="orchestrator",
            options=["a"],
            required_voters=1,
        )

        coordinator.cast_vote(session.id, "agent-1", "a")

        # Verify session is open before tally
        assert db.get_voting_session(session.id).is_open is True

        coordinator.tally_votes(session.id, close_session=True)

        # Verify session is closed after tally
        assert db.get_voting_session(session.id).is_open is False

    def test_cannot_vote_in_closed_session(self, coordinator: VotingCoordinator):
        """Test that voting in closed session fails."""
        session = coordinator.create_session(
            topic="Test closed",
            created_by="orchestrator",
            options=["a"],
            required_voters=1,
        )

        coordinator.cast_vote(session.id, "agent-1", "a")
        coordinator.tally_votes(session.id, close_session=True)

        # Try to vote after close
        result = coordinator.cast_vote(session.id, "agent-2", "a")
        assert result is False

    def test_get_session_status(self, coordinator: VotingCoordinator):
        """Test getting session status."""
        session = coordinator.create_session(
            topic="Status test",
            created_by="orchestrator",
            options=["a", "b"],
            required_voters=3,
        )

        coordinator.cast_vote(session.id, "agent-1", "a")
        coordinator.cast_vote(session.id, "agent-2", "b")

        status = coordinator.get_session_status(session.id)

        assert status["session"].id == session.id
        assert status["total_votes"] == 2
        assert status["quorum_required"] == 3
        assert status["quorum_met"] is False
        assert status["is_open"] is True
