"""Tests for the TrustCircle and AdmissionPolicy modules."""

import pytest
from unittest.mock import MagicMock

from skytale_sdk.attestation import Attestation, create_attestation
from skytale_sdk.identity import AgentIdentity
from skytale_sdk.trust import AdmissionPolicy, TrustCircle


@pytest.fixture
def issuer():
    return AgentIdentity.generate()


@pytest.fixture
def requester():
    return AgentIdentity.generate()


def _make_attestation(issuer, subject_did, claims, **kwargs):
    """Helper to create a signed attestation."""
    return create_attestation(issuer, subject_did, claims, **kwargs)


class TestAdmissionPolicy:
    """AdmissionPolicy dataclass and serialization."""

    def test_default_policy(self):
        policy = AdmissionPolicy()
        assert policy.required_claims == []
        assert policy.min_score is None
        assert policy.required_issuers == []
        assert policy.min_attestations == 1

    def test_to_dict_roundtrip(self):
        policy = AdmissionPolicy(
            required_claims=["score", "cert"],
            min_score=0.8,
            required_issuers=["did:key:z6Mk1"],
            min_attestations=2,
        )
        d = policy.to_dict()
        restored = AdmissionPolicy.from_dict(d)
        assert restored.required_claims == ["score", "cert"]
        assert restored.min_score == 0.8
        assert restored.required_issuers == ["did:key:z6Mk1"]
        assert restored.min_attestations == 2

    def test_from_dict_with_defaults(self):
        policy = AdmissionPolicy.from_dict({})
        assert policy.required_claims == []
        assert policy.min_score is None
        assert policy.min_attestations == 1


class TestEvaluatePolicy:
    """TrustCircle.evaluate_policy() checks attestations against policy."""

    def _make_circle(self, policy):
        mgr = MagicMock()
        return TrustCircle(mgr, "acme/test/circle", policy)

    def test_empty_policy_accepts_any_attestation(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy())
        att = _make_attestation(issuer, requester.did, {"anything": "ok"})
        passed, reasons = circle.evaluate_policy([att])
        assert passed
        assert reasons == []

    def test_required_claims_pass(self, issuer, requester):
        circle = self._make_circle(
            AdmissionPolicy(required_claims=["certification"])
        )
        att = _make_attestation(issuer, requester.did, {"certification": "ISO27001"})
        passed, _ = circle.evaluate_policy([att])
        assert passed

    def test_missing_required_claim_fails(self, issuer, requester):
        circle = self._make_circle(
            AdmissionPolicy(required_claims=["certification"])
        )
        att = _make_attestation(issuer, requester.did, {"other": "value"})
        passed, reasons = circle.evaluate_policy([att])
        assert not passed
        assert any("missing required claim" in r for r in reasons)

    def test_min_score_pass(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_score=0.8))
        att = _make_attestation(issuer, requester.did, {"score": 0.95})
        passed, _ = circle.evaluate_policy([att])
        assert passed

    def test_min_score_fail(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_score=0.8))
        att = _make_attestation(issuer, requester.did, {"score": 0.5})
        passed, reasons = circle.evaluate_policy([att])
        assert not passed
        assert any("below minimum" in r for r in reasons)

    def test_min_score_no_score_claim_fails(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_score=0.8))
        att = _make_attestation(issuer, requester.did, {"other": "value"})
        passed, reasons = circle.evaluate_policy([att])
        assert not passed
        assert any("no 'score'" in r for r in reasons)

    def test_reputation_claim_used_for_score(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_score=0.8))
        att = _make_attestation(issuer, requester.did, {"reputation": 0.9})
        passed, _ = circle.evaluate_policy([att])
        assert passed

    def test_required_issuers_pass(self, issuer, requester):
        circle = self._make_circle(
            AdmissionPolicy(required_issuers=[issuer.did])
        )
        att = _make_attestation(issuer, requester.did, {"x": 1})
        passed, _ = circle.evaluate_policy([att])
        assert passed

    def test_wrong_issuer_fails(self, issuer, requester):
        other = AgentIdentity.generate()
        circle = self._make_circle(
            AdmissionPolicy(required_issuers=[other.did])
        )
        att = _make_attestation(issuer, requester.did, {"x": 1})
        passed, reasons = circle.evaluate_policy([att])
        assert not passed
        assert any("not in required_issuers" in r for r in reasons)

    def test_min_attestations(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_attestations=2))
        att1 = _make_attestation(issuer, requester.did, {"a": 1})
        att2 = _make_attestation(issuer, requester.did, {"b": 2})

        passed_one, _ = circle.evaluate_policy([att1])
        assert not passed_one

        passed_two, _ = circle.evaluate_policy([att1, att2])
        assert passed_two

    def test_combined_policy(self, issuer, requester):
        """Multiple requirements must all be satisfied."""
        circle = self._make_circle(
            AdmissionPolicy(
                required_claims=["certification"],
                min_score=0.8,
                required_issuers=[issuer.did],
            )
        )
        att = _make_attestation(
            issuer, requester.did,
            {"certification": "ISO27001", "score": 0.95},
        )
        passed, _ = circle.evaluate_policy([att])
        assert passed


class TestTrustCircleMembers:
    """TrustCircle membership management."""

    def _make_circle(self, policy=None):
        mgr = MagicMock()
        return TrustCircle(mgr, "acme/test/circle", policy or AdmissionPolicy())

    def test_request_admission_accepted(self, issuer, requester):
        circle = self._make_circle()
        att = _make_attestation(issuer, requester.did, {"x": 1})
        admitted = circle.request_admission(requester.did, [att], b"key_package")
        assert admitted
        assert requester.did in circle.members()

    def test_request_admission_denied(self, issuer, requester):
        circle = self._make_circle(AdmissionPolicy(min_score=0.9))
        att = _make_attestation(issuer, requester.did, {"score": 0.5})
        admitted = circle.request_admission(requester.did, [att], b"key_package")
        assert not admitted
        assert requester.did not in circle.members()

    def test_admission_calls_add_member(self, issuer, requester):
        mgr = MagicMock()
        circle = TrustCircle(mgr, "acme/test/circle", AdmissionPolicy())
        att = _make_attestation(issuer, requester.did, {"x": 1})
        circle.request_admission(requester.did, [att], b"kp_data")
        mgr.add_member.assert_called_once_with("acme/test/circle", b"kp_data")

    def test_revoke_member(self, issuer, requester):
        circle = self._make_circle()
        att = _make_attestation(issuer, requester.did, {"x": 1})
        circle.request_admission(requester.did, [att], b"kp")
        circle.revoke(requester.did)
        assert requester.did not in circle.members()

    def test_revoke_nonmember_raises(self):
        circle = self._make_circle()
        with pytest.raises(KeyError, match="not a member"):
            circle.revoke("did:key:z6MkNobody")

    def test_members_returns_sorted_list(self, issuer):
        circle = self._make_circle()
        dids = []
        for _ in range(3):
            r = AgentIdentity.generate()
            dids.append(r.did)
            att = _make_attestation(issuer, r.did, {"x": 1})
            circle.request_admission(r.did, [att], b"kp")
        assert circle.members() == sorted(dids)


class TestTrustCircleCreate:
    """TrustCircle.create() creates a channel and broadcasts policy."""

    def test_create_calls_manager(self):
        mgr = MagicMock()
        policy = AdmissionPolicy(required_claims=["cert"], min_score=0.7)
        circle = TrustCircle.create(mgr, "acme/trusted/agents", policy)

        mgr.create.assert_called_once_with("acme/trusted/agents")
        mgr.send.assert_called_once()

        # Verify the policy was broadcast
        import json
        sent_msg = mgr.send.call_args[0][1]
        parsed = json.loads(sent_msg)
        assert parsed["type"] == "trust_circle_policy"
        assert parsed["policy"]["required_claims"] == ["cert"]
        assert parsed["policy"]["min_score"] == 0.7

    def test_create_returns_circle_with_policy(self):
        mgr = MagicMock()
        policy = AdmissionPolicy(min_attestations=3)
        circle = TrustCircle.create(mgr, "acme/ns/svc", policy)
        assert circle.policy.min_attestations == 3
