"""Tests for receipt and chain verification."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    from cryptography.hazmat.primitives.asymmetric.ec import (
        EllipticCurvePrivateKey,
        EllipticCurvePublicKey,
    )

from mailio_receipt import (
    Actors,
    Intent,
    IntentKind,
    MailioReceipt,
    chain_from,
    create_event,
    create_receipt,
)
from mailio_receipt.errors import (
    InvalidSignatureError,
    MissingPredecessorError,
    SignerNotFoundError,
)
from mailio_receipt.verification import verify_chain, verify_receipt


class TestVerifyReceipt:
    """Tests for single receipt verification."""

    @pytest.mark.asyncio
    async def test_verify_valid_receipt(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that valid receipt verifies successfully."""
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        result = await verify_receipt(
            receipt, public_keys={agent_did: agent_public_key}
        )

        assert result.valid is True
        assert len(result.signers) == 1
        assert result.signers[0].signer == agent_did
        assert result.signers[0].role == "agent"
        assert result.error is None

    @pytest.mark.asyncio
    async def test_verify_event_digest_mismatch(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that tampered event digest is detected."""
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        tampered_receipt = receipt.__class__(
            type=receipt.type,
            v=receipt.v,
            event=receipt.event,
            event_digest="a" * 64,
            signatures=receipt.signatures,
        )

        result = await verify_receipt(
            tampered_receipt, public_keys={agent_did: agent_public_key}
        )

        assert result.valid is False
        assert "digest mismatch" in result.error.lower()

    @pytest.mark.asyncio
    async def test_verify_invalid_signature(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that wrong public key fails verification."""
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        with pytest.raises(InvalidSignatureError):
            await verify_receipt(
                receipt, public_keys={agent_did: user_public_key}
            )

    @pytest.mark.asyncio
    async def test_verify_missing_signer_key(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that missing signer key raises SignerNotFoundError."""
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        with pytest.raises(SignerNotFoundError):
            await verify_receipt(receipt, public_keys={})

    @pytest.mark.asyncio
    async def test_verify_tampered_event_content(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that tampered event content is detected via signature."""
        original_intent = Intent(
            kind=IntentKind.ACTION_PLANNED,
            action="invoice.send",
            category="financial",
            scope={"amount": 1000},
        )
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=original_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        tampered_intent = Intent(
            kind=IntentKind.ACTION_PLANNED,
            action="invoice.send",
            category="financial",
            scope={"amount": 9999},
        )
        tampered_event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=tampered_intent,
            event_id=receipt.event.id,
            timestamp=None,
        )

        tampered_receipt = receipt.__class__(
            type=receipt.type,
            v=receipt.v,
            event=tampered_event,
            event_digest=receipt.event_digest,
            signatures=receipt.signatures,
        )

        result = await verify_receipt(
            tampered_receipt, public_keys={agent_did: agent_public_key}
        )

        assert result.valid is False


class TestVerifyChain:
    """Tests for chain verification."""

    @pytest.mark.asyncio
    async def test_verify_valid_chain(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        user_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that valid chain verifies successfully."""
        planned_event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=planned_event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_intent = Intent(
            kind="approval",
            action=planned_receipt.event.intent.action,
            category=planned_receipt.event.intent.category,
            scope={"approvedReceiptDigest": planned_receipt.event_digest},
        )
        approval_event = create_event(
            flow_id=sample_flow_id,
            actors=user_actors,
            intent=approval_intent,
            chain=chain_from(planned_receipt),
        )
        approval_receipt = create_receipt(
            event=approval_event,
            private_key=user_private_key,
            signer_did=user_did,
            role="user",
        )

        keys = {agent_did: agent_public_key, user_did: user_public_key}
        result = await verify_chain(
            [planned_receipt, approval_receipt], public_keys=keys
        )

        assert result.valid is True
        assert result.chain_intact is True
        assert len(result.receipt_results) == 2
        assert all(r.valid for r in result.receipt_results)

    @pytest.mark.asyncio
    async def test_verify_chain_missing_predecessor(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        user_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that missing predecessor is detected."""
        planned_event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=planned_event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_intent = Intent(
            kind="approval",
            action=planned_receipt.event.intent.action,
            category=planned_receipt.event.intent.category,
            scope={"approvedReceiptDigest": planned_receipt.event_digest},
        )
        approval_event = create_event(
            flow_id=sample_flow_id,
            actors=user_actors,
            intent=approval_intent,
            chain=chain_from(planned_receipt),
        )
        approval_receipt = create_receipt(
            event=approval_event,
            private_key=user_private_key,
            signer_did=user_did,
            role="user",
        )

        keys = {agent_did: agent_public_key, user_did: user_public_key}

        with pytest.raises(MissingPredecessorError):
            await verify_chain([approval_receipt], public_keys=keys)

    @pytest.mark.asyncio
    async def test_verify_chain_with_invalid_receipt(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        user_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that chain fails if any receipt is invalid."""
        planned_event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=planned_event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_intent = Intent(
            kind="approval",
            action=planned_receipt.event.intent.action,
            category=planned_receipt.event.intent.category,
            scope={"approvedReceiptDigest": planned_receipt.event_digest},
        )
        approval_event = create_event(
            flow_id=sample_flow_id,
            actors=user_actors,
            intent=approval_intent,
            chain=chain_from(planned_receipt),
        )
        approval_receipt = create_receipt(
            event=approval_event,
            private_key=user_private_key,
            signer_did=user_did,
            role="user",
        )

        tampered_approval = approval_receipt.__class__(
            type=approval_receipt.type,
            v=approval_receipt.v,
            event=approval_receipt.event,
            event_digest="b" * 64,
            signatures=approval_receipt.signatures,
        )

        keys = {agent_did: agent_public_key, user_did: user_public_key}
        result = await verify_chain(
            [planned_receipt, tampered_approval], public_keys=keys
        )

        assert result.valid is False
        assert result.chain_intact is False

    @pytest.mark.asyncio
    async def test_verify_single_receipt_chain(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        agent_public_key: EllipticCurvePublicKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test verification of a single receipt (no predecessors)."""
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        result = await verify_chain(
            [receipt], public_keys={agent_did: agent_public_key}
        )

        assert result.valid is True
        assert result.chain_intact is True
