"""Tests for Phase 85: Crypto Identity."""

import json
import pytest
from pathlib import Path

import brynq.crypto_identity as ci


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect storage to tmp_path."""
    monkeypatch.setattr(ci, "IDENTITIES_DIR", tmp_path)
    monkeypatch.setattr(ci, "ATTESTATIONS_FILE", tmp_path / "attestations.jsonl")
    ci.reset()


# ── create_identity ────────────────────────────────────────────────────────

class TestCreateIdentity:
    def test_create_basic(self):
        ident = ci.create_identity("agent-1", "Agent One")
        assert ident["agent_id"] == "agent-1"
        assert ident["display_name"] == "Agent One"
        assert ident["status"] == "active"
        assert "public_id" in ident
        assert "secret_key" not in ident  # should not expose

    def test_create_unique_keys(self):
        i1 = ci.create_identity("a1", "A1")
        i2 = ci.create_identity("a2", "A2")
        assert i1["public_id"] != i2["public_id"]

    def test_create_duplicate_raises(self):
        ci.create_identity("a1", "A1")
        with pytest.raises(ValueError, match="already exists"):
            ci.create_identity("a1", "A1 again")

    def test_create_after_revoke(self):
        ci.create_identity("a1", "A1")
        ci.revoke_identity("a1")
        # Should be able to create again after revocation
        ident = ci.create_identity("a1", "A1 v2")
        assert ident["status"] == "active"

    def test_create_persists(self, tmp_path):
        ci.create_identity("a1", "A1")
        f = tmp_path / "a1.json"
        assert f.exists()
        data = json.loads(f.read_text("utf-8"))
        assert "secret_key" in data  # persisted on disk with key


# ── sign_message / verify_message ──────────────────────────────────────────

class TestSignVerify:
    def test_sign_and_verify(self):
        ci.create_identity("a1", "A1")
        sig = ci.sign_message("a1", "hello world")
        assert isinstance(sig, str)
        assert len(sig) == 64  # SHA-256 hex
        assert ci.verify_message("a1", "hello world", sig) is True

    def test_verify_wrong_message(self):
        ci.create_identity("a1", "A1")
        sig = ci.sign_message("a1", "hello")
        assert ci.verify_message("a1", "goodbye", sig) is False

    def test_verify_wrong_signature(self):
        ci.create_identity("a1", "A1")
        assert ci.verify_message("a1", "hello", "0" * 64) is False

    def test_sign_missing_identity(self):
        with pytest.raises(KeyError):
            ci.sign_message("ghost", "hello")

    def test_sign_revoked_identity(self):
        ci.create_identity("a1", "A1")
        ci.revoke_identity("a1")
        with pytest.raises(ValueError, match="revoked"):
            ci.sign_message("a1", "hello")

    def test_verify_missing_identity(self):
        assert ci.verify_message("ghost", "hello", "abc") is False

    def test_sign_different_messages_differ(self):
        ci.create_identity("a1", "A1")
        s1 = ci.sign_message("a1", "msg1")
        s2 = ci.sign_message("a1", "msg2")
        assert s1 != s2

    def test_sign_same_message_consistent(self):
        ci.create_identity("a1", "A1")
        s1 = ci.sign_message("a1", "same")
        s2 = ci.sign_message("a1", "same")
        assert s1 == s2

    def test_verify_cross_agent(self):
        ci.create_identity("a1", "A1")
        ci.create_identity("a2", "A2")
        sig = ci.sign_message("a1", "hello")
        # a2 should not verify a1's signature
        assert ci.verify_message("a2", "hello", sig) is False


# ── get_identity / list_identities ─────────────────────────────────────────

class TestGetAndList:
    def test_get_existing(self):
        ci.create_identity("a1", "A1")
        ident = ci.get_identity("a1")
        assert ident["agent_id"] == "a1"
        assert "secret_key" not in ident

    def test_get_missing(self):
        assert ci.get_identity("ghost") is None

    def test_list_all(self):
        ci.create_identity("a1", "A1")
        ci.create_identity("a2", "A2")
        ids = ci.list_identities()
        assert len(ids) == 2
        for i in ids:
            assert "secret_key" not in i

    def test_list_empty(self):
        assert ci.list_identities() == []


# ── revoke_identity ────────────────────────────────────────────────────────

class TestRevokeIdentity:
    def test_revoke_existing(self):
        ci.create_identity("a1", "A1")
        assert ci.revoke_identity("a1", reason="compromised") is True
        ident = ci.get_identity("a1")
        assert ident["status"] == "revoked"
        assert ident["revoke_reason"] == "compromised"

    def test_revoke_missing(self):
        assert ci.revoke_identity("ghost") is False


# ── create_attestation / verify_attestation ────────────────────────────────

class TestAttestations:
    def test_create_attestation(self):
        ci.create_identity("a1", "A1")
        att = ci.create_attestation("a1", "I completed task 42")
        assert att["agent_id"] == "a1"
        assert att["claim"] == "I completed task 42"
        assert "signature" in att
        assert "payload" in att

    def test_create_with_evidence(self):
        ci.create_identity("a1", "A1")
        att = ci.create_attestation("a1", "Built feature", evidence="commit abc123")
        assert att["evidence"] == "commit abc123"

    def test_verify_attestation_valid(self):
        ci.create_identity("a1", "A1")
        att = ci.create_attestation("a1", "Did the thing")
        assert ci.verify_attestation(att) is True

    def test_verify_attestation_tampered(self):
        ci.create_identity("a1", "A1")
        att = ci.create_attestation("a1", "Did the thing")
        att["signature"] = "0" * 64
        assert ci.verify_attestation(att) is False

    def test_verify_attestation_missing_agent(self):
        ci.create_identity("a1", "A1")
        att = ci.create_attestation("a1", "claim")
        att["agent_id"] = "ghost"
        assert ci.verify_attestation(att) is False

    def test_verify_attestation_no_agent_field(self):
        assert ci.verify_attestation({"claim": "bad"}) is False

    def test_attestation_missing_identity(self):
        with pytest.raises(KeyError):
            ci.create_attestation("ghost", "claim")

    def test_attestation_revoked_identity(self):
        ci.create_identity("a1", "A1")
        ci.revoke_identity("a1")
        with pytest.raises(ValueError, match="revoked"):
            ci.create_attestation("a1", "claim")

    def test_attestation_persists(self, tmp_path):
        ci.create_identity("a1", "A1")
        ci.create_attestation("a1", "stored")
        f = tmp_path / "attestations.jsonl"
        assert f.exists()
        lines = f.read_text("utf-8").strip().splitlines()
        assert len(lines) == 1
        entry = json.loads(lines[0])
        assert entry["claim"] == "stored"
