"""Integration tests for complete workflows."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey

from mailio_receipt import (
    Actors,
    Intent,
    IntentKind,
    create_event,
    create_receipt,
)
from mailio_receipt.execution import BlockingReason, check_execution
from tests.conftest import (
    build_approval_receipt,
    build_execution_outcome_receipt,
    build_revocation_receipt,
)


class TestApprovalWorkflow:
    """Tests for approval grant workflow."""

    def test_create_approval_with_chain_reference(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that approval receipt correctly chains to the approved receipt."""
        # Create planned action receipt
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval receipt
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Verify chain reference
        assert approval_receipt.event.chain is not None
        assert approval_receipt.event.chain.prev_receipt_digest == planned_receipt.event_digest

        # Verify intent kind
        assert approval_receipt.event.intent.kind == IntentKind.APPROVAL_GRANTED

        # Verify scope contains approved digest
        assert "approvedReceiptDigest" in approval_receipt.event.intent.scope
        assert (
            approval_receipt.event.intent.scope["approvedReceiptDigest"]
            == planned_receipt.event_digest
        )

    def test_approval_with_ttl(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that approval can include TTL."""
        # Create planned action receipt
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval with TTL
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
            ttl="PT1H",  # 1 hour
        )

        # Verify TTL is set
        assert approval_receipt.event.ttl == "PT1H"

    def test_approval_preserves_action_context(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that approval preserves action and category from original."""
        # Create planned action receipt
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Action and category should match original
        assert approval_receipt.event.intent.action == planned_receipt.event.intent.action
        assert approval_receipt.event.intent.category == planned_receipt.event.intent.category


class TestRevocationWorkflow:
    """Tests for revocation workflow."""

    def test_revocation_chains_to_approval(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that revocation receipt correctly chains to the approval."""
        # Create planned action receipt
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Create revocation
        revocation_receipt = build_revocation_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Verify chain reference
        assert revocation_receipt.event.chain is not None
        assert (
            revocation_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )

        # Verify intent kind
        assert revocation_receipt.event.intent.kind == IntentKind.APPROVAL_REVOKED

        # Verify scope contains revoked digest
        assert "revokedApprovalDigest" in revocation_receipt.event.intent.scope
        assert (
            revocation_receipt.event.intent.scope["revokedApprovalDigest"]
            == approval_receipt.event_digest
        )

    def test_revocation_includes_reason(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that revocation includes the reason."""
        # Setup workflow
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Create revocation with reason
        revocation_receipt = build_revocation_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
            scope={"reason": "Invoice amount is incorrect"},
        )

        # Verify reason in scope
        assert revocation_receipt.event.intent.scope["reason"] == "Invoice amount is incorrect"


class TestCompleteWorkflow:
    """Tests for complete end-to-end workflows."""

    def test_full_workflow_planned_approved_executed(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test complete workflow: planned → approved → executed."""
        # Step 1: Plan action
        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,
        )

        # Step 2: Approve
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Step 3: Execute (record outcome)
        executed_receipt = build_execution_outcome_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=sample_actors,
            success=True,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Verify chain integrity
        assert executed_receipt.event.chain is not None
        assert (
            executed_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )
        assert approval_receipt.event.chain is not None
        assert (
            approval_receipt.event.chain.prev_receipt_digest
            == planned_receipt.event_digest
        )

        # Verify all receipts have unique digests
        digests = {planned_receipt.digest, approval_receipt.digest, executed_receipt.digest}
        assert len(digests) == 3

    def test_workflow_with_revocation(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test workflow with revocation: planned → approved → revoked."""
        # Step 1: Plan action
        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,
        )

        # Step 2: Approve
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Step 3: Revoke
        revocation_receipt = build_revocation_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
            scope={"reason": "Found error in invoice"},
        )

        # Verify revocation chains to approval
        assert revocation_receipt.event.chain is not None
        assert (
            revocation_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )

        # Verify revocation intent
        assert revocation_receipt.event.intent.kind == IntentKind.APPROVAL_REVOKED


class TestExecutionOutcomeReceipt:
    """Tests for execution outcome receipt creation."""

    def test_execution_outcome_success(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test creating a successful execution outcome receipt."""
        # Create planned action
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Verify execution is allowed
        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
        )
        assert decision.allowed is True

        # Create execution outcome (success)
        outcome_receipt = build_execution_outcome_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=sample_actors,
            success=True,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Verify outcome receipt
        assert outcome_receipt.event.intent.kind == IntentKind.ACTION_EXECUTED
        assert outcome_receipt.event.chain is not None
        assert (
            outcome_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )

    def test_execution_outcome_failure(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test creating a failed execution outcome receipt."""
        # Create planned action
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Create execution outcome (failure)
        outcome_receipt = build_execution_outcome_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=sample_actors,
            success=False,
            failure_reason="Network timeout",
            failure_code="ERR_TIMEOUT",
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Verify outcome receipt
        assert outcome_receipt.event.intent.kind == IntentKind.ACTION_FAILED
        assert outcome_receipt.event.intent.scope["failureReason"] == "Network timeout"
        assert outcome_receipt.event.intent.scope["failureCode"] == "ERR_TIMEOUT"
        assert outcome_receipt.event.chain is not None
        assert (
            outcome_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )


class TestCompleteWorkflowChainIntegrity:
    """Tests for complete workflow chain integrity."""

    def test_complete_workflow_chain(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test complete workflow: planned -> approved -> executed."""
        # Step 1: Create planned action
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Step 2: Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Step 3: Verify execution eligibility
        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
        )
        assert decision.allowed is True
        assert decision.approval_digest == approval_receipt.digest

        # Step 4: Create execution outcome
        outcome_receipt = build_execution_outcome_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=sample_actors,
            success=True,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Verify complete chain
        chain = [planned_receipt, approval_receipt, outcome_receipt]

        # All receipts have valid digests
        for receipt in chain:
            assert len(receipt.digest) == 64
            assert all(c in "0123456789abcdef" for c in receipt.digest)

        # Chain linkage is correct
        assert (
            approval_receipt.event.chain.prev_receipt_digest
            == planned_receipt.event_digest
        )
        assert (
            outcome_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )

        # All receipts share the same flow_id
        flow_ids = {r.event.flow_id for r in chain}
        assert len(flow_ids) == 1
        assert sample_flow_id in flow_ids

        # Intent kinds are correct in sequence
        assert planned_receipt.event.intent.kind == IntentKind.ACTION_PLANNED
        assert approval_receipt.event.intent.kind == IntentKind.APPROVAL_GRANTED
        assert outcome_receipt.event.intent.kind == IntentKind.ACTION_EXECUTED

    def test_workflow_with_revocation_blocks_execution(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test workflow: planned -> approved -> revoked (execution blocked)."""
        # Step 1: Create planned action
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=sample_intent,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Step 2: Create approval
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_receipt = build_approval_receipt(
            planned_receipt=planned_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
        )

        # Step 3: Revoke approval
        revocation_receipt = build_revocation_receipt(
            approval_receipt=approval_receipt,
            flow_id=sample_flow_id,
            actors=user_actors,
            private_key=user_private_key,
            signer_did=user_did,
            scope={"reason": "Changed mind"},
        )

        # Step 4: Verify execution is blocked
        decision = check_execution(
            [planned_receipt, approval_receipt, revocation_receipt],
            action_digest=planned_receipt.event_digest,
        )
        assert decision.allowed is False
        assert decision.blocking_reason == BlockingReason.REVOKED
        assert decision.revocation_digest == revocation_receipt.digest

        # Verify chain linkage
        assert (
            approval_receipt.event.chain.prev_receipt_digest
            == planned_receipt.event_digest
        )
        assert (
            revocation_receipt.event.chain.prev_receipt_digest
            == approval_receipt.event_digest
        )
