"""Tests for brynq.swarm — dynamic swarm formation and consensus."""

import json
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_swarm(tmp_path, monkeypatch):
    """Redirect swarm storage to temp directory."""
    swarm_dir = tmp_path / "swarms"
    swarm_dir.mkdir()
    monkeypatch.setattr("brynq.swarm.SWARM_DIR", swarm_dir)


class TestCreateSwarm:
    def test_returns_swarm_id(self):
        from brynq.swarm import create_swarm
        swarm_id = create_swarm("task-1", ["python", "testing"])
        assert isinstance(swarm_id, str)
        assert len(swarm_id) == 12

    def test_creates_swarm_file(self):
        from brynq.swarm import create_swarm, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"])
        assert (SWARM_DIR / f"{swarm_id}.json").exists()

    def test_swarm_initial_state(self):
        from brynq.swarm import create_swarm, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"], max_size=5)
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert data["status"] == "forming"
        assert data["task_id"] == "task-1"
        assert data["required_skills"] == ["python"]
        assert data["max_size"] == 5
        assert data["members"] == {}
        assert data["bids"] == []


class TestBidding:
    def test_submit_bid(self):
        from brynq.swarm import create_swarm, submit_bid, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"])
        assert submit_bid(swarm_id, "agent-1", reputation=80, skill_match=0.9) is True
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert len(data["bids"]) == 1
        assert data["bids"][0]["session_id"] == "agent-1"

    def test_multiple_bids(self):
        from brynq.swarm import create_swarm, submit_bid, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"])
        submit_bid(swarm_id, "agent-1", reputation=80, skill_match=0.9)
        submit_bid(swarm_id, "agent-2", reputation=90, skill_match=0.8)
        submit_bid(swarm_id, "agent-3", reputation=70, skill_match=1.0)
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert len(data["bids"]) == 3

    def test_bid_on_nonexistent_returns_false(self):
        from brynq.swarm import submit_bid
        assert submit_bid("nonexistent", "agent-1", 80, 0.9) is False

    def test_bid_on_active_swarm_returns_false(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm
        swarm_id = create_swarm("task-1", ["python"])
        submit_bid(swarm_id, "agent-1", 80, 0.9)
        form_swarm(swarm_id)
        assert submit_bid(swarm_id, "agent-2", 90, 1.0) is False


class TestFormSwarm:
    def test_form_assigns_roles(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"], max_size=3)
        submit_bid(swarm_id, "agent-1", reputation=90, skill_match=1.0)
        submit_bid(swarm_id, "agent-2", reputation=80, skill_match=0.8)
        submit_bid(swarm_id, "agent-3", reputation=70, skill_match=0.7)
        assert form_swarm(swarm_id) is True
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert data["status"] == "active"
        assert "agent-1" in data["members"]
        assert data["members"]["agent-1"] == "lead"

    def test_form_respects_max_size(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"], max_size=2)
        submit_bid(swarm_id, "a1", 90, 1.0)
        submit_bid(swarm_id, "a2", 80, 0.8)
        submit_bid(swarm_id, "a3", 70, 0.7)
        form_swarm(swarm_id)
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert len(data["members"]) == 2

    def test_form_no_bids_returns_false(self):
        from brynq.swarm import create_swarm, form_swarm
        swarm_id = create_swarm("task-1", ["python"])
        assert form_swarm(swarm_id) is False

    def test_form_nonexistent_returns_false(self):
        from brynq.swarm import form_swarm
        assert form_swarm("nonexistent") is False


class TestConsensus:
    def test_propose_and_vote(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, propose_decision, vote_on_decision
        swarm_id = create_swarm("task-1", ["python"], max_size=3)
        submit_bid(swarm_id, "a1", 90, 1.0)
        submit_bid(swarm_id, "a2", 80, 0.9)
        submit_bid(swarm_id, "a3", 70, 0.8)
        form_swarm(swarm_id)
        assert propose_decision(swarm_id, "prop-1", "Use FastAPI") is True
        result = vote_on_decision(swarm_id, "prop-1", "a1", True)
        assert result in ("pending", "approved")

    def test_quorum_approval(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, propose_decision, vote_on_decision
        swarm_id = create_swarm("task-1", ["python"], max_size=3)
        submit_bid(swarm_id, "a1", 90, 1.0)
        submit_bid(swarm_id, "a2", 80, 0.9)
        submit_bid(swarm_id, "a3", 70, 0.8)
        form_swarm(swarm_id)
        propose_decision(swarm_id, "prop-1", "Use FastAPI")
        # Quorum = max(1, int(3*0.66)) = 1, so first yes vote approves
        result = vote_on_decision(swarm_id, "prop-1", "a1", True)
        assert result == "approved"

    def test_duplicate_proposal_returns_false(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, propose_decision
        swarm_id = create_swarm("task-1", ["python"])
        submit_bid(swarm_id, "a1", 90, 1.0)
        form_swarm(swarm_id)
        assert propose_decision(swarm_id, "prop-1", "Desc") is True
        assert propose_decision(swarm_id, "prop-1", "Desc again") is False

    def test_non_member_vote_returns_error(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, propose_decision, vote_on_decision
        swarm_id = create_swarm("task-1", ["python"])
        submit_bid(swarm_id, "a1", 90, 1.0)
        form_swarm(swarm_id)
        propose_decision(swarm_id, "prop-1", "Desc")
        assert vote_on_decision(swarm_id, "prop-1", "outsider", True) == "error"


class TestDissolve:
    def test_dissolve_swarm(self):
        from brynq.swarm import create_swarm, submit_bid, form_swarm, dissolve_swarm, SWARM_DIR
        swarm_id = create_swarm("task-1", ["python"])
        submit_bid(swarm_id, "a1", 90, 1.0)
        form_swarm(swarm_id)
        assert dissolve_swarm(swarm_id) is True
        data = json.loads((SWARM_DIR / f"{swarm_id}.json").read_text("utf-8"))
        assert data["status"] == "dissolved"
        assert "dissolved_at" in data

    def test_dissolve_nonexistent_returns_false(self):
        from brynq.swarm import dissolve_swarm
        assert dissolve_swarm("nonexistent") is False
