"""Tests for uam.protocol.contact module."""

from __future__ import annotations

import dataclasses

import pytest

from uam.protocol.contact import (
    ContactCard,
    contact_card_from_dict,
    contact_card_to_dict,
    create_contact_card,
    verify_contact_card,
)
from uam.protocol.crypto import generate_keypair, serialize_verify_key
from uam.protocol.errors import (
    InvalidAddressError,
    InvalidContactCardError,
    SignatureVerificationError,
)


# ---------------------------------------------------------------------------
# Creation
# ---------------------------------------------------------------------------

class TestCreation:
    def test_all_fields_present(self, keypair):
        sk, vk = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice Agent",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        assert card.version is not None
        assert card.address is not None
        assert card.display_name is not None
        assert card.relay is not None
        assert card.public_key is not None
        assert card.signature is not None

    def test_version_is_0_1(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        assert card.version == "0.1"

    def test_public_key_matches(self, keypair):
        sk, vk = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        assert card.public_key == serialize_verify_key(vk)

    def test_address_preserved(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        assert card.address == "alice::youam.network"

    def test_optional_fields_none_when_not_provided(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        assert card.description is None
        assert card.system is None
        assert card.connection_endpoint is None
        assert card.verified_domain is None

    def test_optional_fields_present_when_provided(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            description="A helpful agent",
            system="openai",
            connection_endpoint="https://alice.example.com",
        )
        assert card.description == "A helpful agent"
        assert card.system == "openai"
        assert card.connection_endpoint == "https://alice.example.com"


# ---------------------------------------------------------------------------
# Signing and verification
# ---------------------------------------------------------------------------

class TestSigningVerification:
    def test_verify_succeeds(self, sample_contact_card):
        verify_contact_card(sample_contact_card)  # should not raise

    def test_tampered_display_name_fails(self, sample_contact_card):
        tampered = dataclasses.replace(sample_contact_card, display_name="Evil Agent")
        with pytest.raises(SignatureVerificationError):
            verify_contact_card(tampered)

    def test_tampered_address_fails(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        tampered = dataclasses.replace(card, address="eve::youam.network")
        with pytest.raises(SignatureVerificationError):
            verify_contact_card(tampered)

    def test_self_verifying(self, keypair):
        """Card embeds public_key so any recipient can verify without external key."""
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        # verify_contact_card uses the embedded public_key, not an external key
        verify_contact_card(card)


# ---------------------------------------------------------------------------
# Serialization
# ---------------------------------------------------------------------------

class TestSerialization:
    def test_roundtrip(self, sample_contact_card):
        d = contact_card_to_dict(sample_contact_card)
        restored = contact_card_from_dict(d)
        assert restored == sample_contact_card

    def test_excludes_none_optionals(self, keypair):
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        d = contact_card_to_dict(card)
        assert "description" not in d
        assert "system" not in d
        assert "connection_endpoint" not in d

    def test_missing_required_raises(self):
        with pytest.raises(InvalidContactCardError):
            contact_card_from_dict({"version": "0.1"}, verify=False)

    def test_from_dict_verifies_by_default(self, sample_contact_card):
        d = contact_card_to_dict(sample_contact_card)
        d["display_name"] = "Tampered"  # invalidate signature
        with pytest.raises(SignatureVerificationError):
            contact_card_from_dict(d)

    def test_from_dict_verify_false_skips_check(self, sample_contact_card):
        d = contact_card_to_dict(sample_contact_card)
        d["display_name"] = "Tampered"
        card = contact_card_from_dict(d, verify=False)  # should not raise
        assert card.display_name == "Tampered"

    def test_includes_optional_when_present(self, sample_contact_card):
        d = contact_card_to_dict(sample_contact_card)
        # sample_contact_card has description="A helpful agent"
        assert d["description"] == "A helpful agent"


# ---------------------------------------------------------------------------
# Validation
# ---------------------------------------------------------------------------

class TestValidation:
    def test_invalid_address_raises(self, keypair):
        sk, _ = keypair
        with pytest.raises(InvalidAddressError):
            create_contact_card(
                address="invalid-address",
                display_name="Alice",
                relay="wss://relay.youam.network",
                signing_key=sk,
            )


# ---------------------------------------------------------------------------
# Immutability
# ---------------------------------------------------------------------------

class TestImmutability:
    def test_contact_card_is_frozen(self, sample_contact_card):
        with pytest.raises(AttributeError):
            sample_contact_card.display_name = "Tampered"  # type: ignore[misc]


# ---------------------------------------------------------------------------
# Verified domain (DNS-06)
# ---------------------------------------------------------------------------


class TestVerifiedDomain:
    def test_none_produces_same_signature(self, keypair):
        """ContactCard with verified_domain=None should produce the same
        signature as one created without the field at all (backward compat)."""
        sk, _ = keypair
        card_without = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        card_with_none = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            verified_domain=None,
        )
        assert card_without.signature == card_with_none.signature

    def test_verified_domain_in_signable_dict(self, keypair):
        """ContactCard with verified_domain set should include it in the
        signable dict, changing the signature."""
        from uam.protocol.contact import _build_signable_dict

        sk, _ = keypair
        card_none = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            verified_domain=None,
        )
        card_domain = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            verified_domain="example.com",
        )
        d_none = _build_signable_dict(card_none)
        d_domain = _build_signable_dict(card_domain)
        assert "verified_domain" not in d_none
        assert d_domain["verified_domain"] == "example.com"
        # Signatures must differ
        assert card_none.signature != card_domain.signature

    def test_verified_domain_roundtrip(self, keypair):
        """Serialization and deserialization preserves verified_domain."""
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            verified_domain="example.com",
        )
        d = contact_card_to_dict(card)
        assert d["verified_domain"] == "example.com"
        restored = contact_card_from_dict(d)
        assert restored.verified_domain == "example.com"
        assert restored == card

    def test_verified_domain_none_roundtrip(self, keypair):
        """Cards without verified_domain serialize without the field."""
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
        )
        d = contact_card_to_dict(card)
        assert "verified_domain" not in d
        restored = contact_card_from_dict(d)
        assert restored.verified_domain is None
        assert restored == card

    def test_verified_card_verifies(self, keypair):
        """A card with verified_domain passes signature verification."""
        sk, _ = keypair
        card = create_contact_card(
            address="alice::youam.network",
            display_name="Alice",
            relay="wss://relay.youam.network",
            signing_key=sk,
            verified_domain="example.com",
        )
        verify_contact_card(card)  # should not raise
