"""
Tests for Phase 23: Federation — cross-instance agent marketplace.
"""

import json
from pathlib import Path

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all storage to temp directory."""
    channels = tmp_path / "channels"
    sessions = tmp_path / "sessions"
    federation = tmp_path / "federation"
    peers = federation / "peers"
    receipts = federation / "receipts"

    for d in (channels, sessions, federation, peers, receipts):
        d.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)
    monkeypatch.setattr(srv, "CHANNELS_DIR", channels)
    monkeypatch.setattr(srv, "SESSIONS_DIR", sessions)

    import brynq.federation as fed
    monkeypatch.setattr(fed, "FEDERATION_DIR", federation)
    monkeypatch.setattr(fed, "PEERS_DIR", peers)
    monkeypatch.setattr(fed, "RECEIPTS_DIR", receipts)
    monkeypatch.setattr(fed, "INSTANCE_FILE", federation / "instance.json")
    monkeypatch.setattr(fed, "REGISTRY_LOG", federation / "registry.jsonl")
    monkeypatch.setattr(fed, "CHANNELS_DIR", channels)


# ── Instance Identity ──────────────────────────────────────────────────────


class TestInstance:
    def test_init_instance(self):
        from brynq.federation import init_instance
        inst = init_instance(name="TestBrynq", public_url="https://test.brynq.ai")
        assert inst["instance_id"]
        assert inst["name"] == "TestBrynq"
        assert inst["public_url"] == "https://test.brynq.ai"
        assert inst["signing_secret"]

    def test_init_preserves_id(self):
        from brynq.federation import init_instance
        inst1 = init_instance(name="First")
        inst2 = init_instance(name="Updated")
        assert inst2["instance_id"] == inst1["instance_id"]
        assert inst2["name"] == "Updated"

    def test_get_instance(self):
        from brynq.federation import init_instance, get_instance
        init_instance(name="MyInstance")
        inst = get_instance()
        assert inst["name"] == "MyInstance"

    def test_get_instance_empty(self):
        from brynq.federation import get_instance
        assert get_instance() == {}

    def test_update_stats(self):
        from brynq.federation import init_instance, update_instance_stats, get_instance
        init_instance(name="Stats")
        update_instance_stats(agent_count=12, task_count=45)
        inst = get_instance()
        assert inst["agent_count"] == 12
        assert inst["task_count"] == 45

    def test_update_stats_no_instance(self):
        from brynq.federation import update_instance_stats
        result = update_instance_stats(agent_count=5)
        assert "error" in result


# ── Peer Management ────────────────────────────────────────────────────────


class TestPeers:
    def test_add_peer(self):
        from brynq.federation import add_peer
        peer = add_peer("peer-1", "Acme Brynq", "https://acme.brynq.ai", trust_level="standard")
        assert peer["peer_id"] == "peer-1"
        assert peer["trust_level"] == "standard"
        assert peer["status"] == "active"

    def test_add_peer_invalid_trust(self):
        from brynq.federation import add_peer
        result = add_peer("peer-2", "Bad", "http://x", trust_level="super")
        assert "error" in result

    def test_get_peer(self):
        from brynq.federation import add_peer, get_peer
        add_peer("peer-3", "Test", "http://test")
        peer = get_peer("peer-3")
        assert peer["name"] == "Test"

    def test_get_nonexistent_peer(self):
        from brynq.federation import get_peer
        assert get_peer("nope") is None

    def test_list_peers(self):
        from brynq.federation import add_peer, list_peers
        add_peer("p1", "A", "http://a")
        add_peer("p2", "B", "http://b", trust_level="trusted")
        peers = list_peers()
        assert len(peers) == 2

    def test_list_peers_filter_trust(self):
        from brynq.federation import add_peer, list_peers
        add_peer("p1", "Standard", "http://a", trust_level="standard")
        add_peer("p2", "Trusted", "http://b", trust_level="trusted")
        trusted = list_peers(trust_level="trusted")
        assert len(trusted) == 1
        assert trusted[0]["name"] == "Trusted"

    def test_remove_peer(self):
        from brynq.federation import add_peer, remove_peer, get_peer
        add_peer("p-del", "Delete Me", "http://x")
        assert remove_peer("p-del") is True
        assert get_peer("p-del") is None

    def test_remove_nonexistent(self):
        from brynq.federation import remove_peer
        assert remove_peer("nope") is False

    def test_update_trust(self):
        from brynq.federation import add_peer, update_peer_trust, get_peer
        add_peer("p-up", "Upgrade", "http://x", trust_level="discovery")
        update_peer_trust("p-up", "trusted")
        peer = get_peer("p-up")
        assert peer["trust_level"] == "trusted"

    def test_update_trust_invalid(self):
        from brynq.federation import add_peer, update_peer_trust
        add_peer("p-bad", "Bad", "http://x")
        result = update_peer_trust("p-bad", "ultra")
        assert "error" in result

    def test_update_trust_nonexistent(self):
        from brynq.federation import update_peer_trust
        result = update_peer_trust("nope", "standard")
        assert "error" in result


# ── Trust Receipts ─────────────────────────────────────────────────────────


class TestReceipts:
    def _init(self):
        from brynq.federation import init_instance
        return init_instance(name="Receipts Test")

    def test_issue_receipt(self):
        self._init()
        from brynq.federation import issue_receipt
        r = issue_receipt("agent-1", "TestAgent", "task-1", "Fix bug", 4.5, "Good work")
        assert r["receipt_id"]
        assert r["rating"] == 4.5
        assert r["signature"]

    def test_receipt_rating_clamp(self):
        self._init()
        from brynq.federation import issue_receipt
        r = issue_receipt("a", "A", "t", "T", 10.0)
        assert r["rating"] == 5.0
        r2 = issue_receipt("a", "A", "t", "T", -1.0)
        assert r2["rating"] == 1.0

    def test_verify_receipt_valid(self):
        self._init()
        from brynq.federation import issue_receipt, verify_receipt
        r = issue_receipt("a", "A", "t", "T", 4.0)
        result = verify_receipt(r)
        assert result["valid"] is True

    def test_verify_receipt_tampered(self):
        self._init()
        from brynq.federation import issue_receipt, verify_receipt
        r = issue_receipt("a", "A", "t", "T", 4.0)
        r["rating"] = 5.0  # tamper
        result = verify_receipt(r)
        assert result["valid"] is False

    def test_verify_receipt_wrong_secret(self):
        self._init()
        from brynq.federation import issue_receipt, verify_receipt
        r = issue_receipt("a", "A", "t", "T", 4.0)
        result = verify_receipt(r, signing_secret="wrong-secret")
        assert result["valid"] is False

    def test_issue_no_instance(self):
        from brynq.federation import issue_receipt
        result = issue_receipt("a", "A", "t", "T", 4.0)
        assert "error" in result

    def test_list_receipts(self):
        self._init()
        from brynq.federation import issue_receipt, list_receipts
        issue_receipt("a1", "A1", "t1", "T1", 4.0)
        issue_receipt("a1", "A1", "t2", "T2", 5.0)
        issue_receipt("a2", "A2", "t3", "T3", 3.0)
        all_r = list_receipts()
        assert len(all_r) == 3

    def test_list_receipts_filter_agent(self):
        self._init()
        from brynq.federation import issue_receipt, list_receipts
        issue_receipt("a1", "A1", "t1", "T1", 4.0)
        issue_receipt("a2", "A2", "t2", "T2", 5.0)
        a1_receipts = list_receipts(agent_id="a1")
        assert len(a1_receipts) == 1

    def test_list_receipts_min_rating(self):
        self._init()
        from brynq.federation import issue_receipt, list_receipts
        issue_receipt("a", "A", "t1", "T1", 2.0)
        issue_receipt("a", "A", "t2", "T2", 4.5)
        good = list_receipts(min_rating=4.0)
        assert len(good) == 1

    def test_agent_federation_reputation(self):
        self._init()
        from brynq.federation import issue_receipt, get_agent_federation_reputation
        issue_receipt("rep-agent", "RepAgent", "t1", "T1", 5.0)
        issue_receipt("rep-agent", "RepAgent", "t2", "T2", 4.0)
        rep = get_agent_federation_reputation("rep-agent")
        assert rep["total_receipts"] == 2
        assert rep["avg_rating"] == 4.5
        assert rep["instances_worked"] >= 1
        assert rep["reputation_score"] > 0

    def test_agent_no_reputation(self):
        from brynq.federation import get_agent_federation_reputation
        rep = get_agent_federation_reputation("nobody")
        assert rep["total_receipts"] == 0
        assert rep["reputation_score"] == 0.0


# ── Federated Jobs ─────────────────────────────────────────────────────────


class TestFederatedJobs:
    def _setup(self):
        from brynq.federation import init_instance, add_peer
        init_instance(name="JobOrigin", public_url="https://origin.brynq.ai")
        add_peer("peer-a", "Peer A", "https://a.brynq.ai", trust_level="standard")
        add_peer("peer-b", "Peer B", "https://b.brynq.ai", trust_level="trusted")

    def test_post_federated_job(self):
        self._setup()
        from brynq.federation import post_federated_job
        job = post_federated_job("Build API", "REST endpoints", ["python", "fastapi"])
        assert job["fed_job_id"]
        assert job["status"] == "open"
        assert len(job["broadcast_to"]) == 2  # both peers are standard+

    def test_post_to_specific_peer(self):
        self._setup()
        from brynq.federation import post_federated_job
        job = post_federated_job("ML Training", "Train model", ["pytorch"], target_peer_id="peer-a")
        assert job["target_peer"] == "peer-a"
        assert len(job["broadcast_to"]) == 1

    def test_post_no_instance(self):
        from brynq.federation import post_federated_job
        result = post_federated_job("Job", "desc", ["python"])
        assert "error" in result

    def test_list_federated_jobs(self):
        self._setup()
        from brynq.federation import post_federated_job, list_federated_jobs
        post_federated_job("Job 1", "d1", ["python"])
        post_federated_job("Job 2", "d2", ["rust"])
        jobs = list_federated_jobs()
        assert len(jobs) == 2

    def test_list_federated_jobs_filter(self):
        self._setup()
        from brynq.federation import post_federated_job, list_federated_jobs
        post_federated_job("Open", "d", ["python"])
        jobs = list_federated_jobs(status="open")
        assert len(jobs) == 1


# ── Federation Stats ───────────────────────────────────────────────────────


class TestFederationStats:
    def test_stats_empty(self):
        from brynq.federation import get_federation_stats
        stats = get_federation_stats()
        assert stats["total_peers"] == 0

    def test_stats_with_data(self):
        from brynq.federation import init_instance, add_peer, issue_receipt, get_federation_stats
        init_instance(name="StatsTest")
        add_peer("s1", "S1", "http://s1", trust_level="trusted")
        add_peer("s2", "S2", "http://s2")
        issue_receipt("a", "A", "t", "T", 5.0)
        stats = get_federation_stats()
        assert stats["total_peers"] == 2
        assert stats["trusted_peers"] == 1
        assert stats["total_receipts"] == 1

    def test_federation_log(self):
        from brynq.federation import init_instance, get_federation_log
        init_instance(name="LogTest")
        log = get_federation_log()
        assert len(log) >= 1
        assert log[-1]["event"] == "init"

    def test_empty_log(self):
        from brynq.federation import get_federation_log
        assert get_federation_log() == []
