"""Tests for execution eligibility checks."""

from __future__ import annotations

from datetime import datetime, timedelta
from typing import TYPE_CHECKING

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

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


class TestCheckExecutionAllowed:
    """Tests for allowed execution scenarios."""

    def test_execution_allowed_with_valid_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 execution is allowed with valid approval."""
        # 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,
        )

        # Check execution
        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
        assert decision.blocking_reason is None


class TestCheckExecutionBlocked:
    """Tests for blocked execution scenarios."""

    def test_execution_blocked_without_approval(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_intent: Intent,
        sample_flow_id: str,
        agent_did: str,
    ) -> None:
        """Test that execution is blocked without approval."""
        # Create planned action only (no approval)
        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,
        )

        # Check execution
        decision = check_execution(
            [planned_receipt],
            action_digest=planned_receipt.event_digest,
        )

        assert decision.allowed is False
        assert decision.blocking_reason == BlockingReason.MISSING_APPROVAL

    def test_execution_blocked_by_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 that execution is blocked when approval is revoked."""
        # Create workflow: planned -> approved -> revoked
        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,
        )

        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,
        )

        # Check execution with decision time AFTER revocation
        revocation_ts = datetime.fromisoformat(revocation_receipt.event.ts)
        decision_time = revocation_ts + timedelta(seconds=1)

        decision = check_execution(
            [planned_receipt, approval_receipt, revocation_receipt],
            action_digest=planned_receipt.event_digest,
            decision_time=decision_time,
        )

        assert decision.allowed is False
        assert decision.blocking_reason == BlockingReason.REVOKED
        assert decision.revocation_digest == revocation_receipt.digest

    def test_execution_allowed_when_revocation_after_decision(
        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 execution is allowed when revocation comes after decision."""
        # Create 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,
        )

        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,
        )

        # Check execution with decision time BEFORE revocation
        revocation_ts = datetime.fromisoformat(revocation_receipt.event.ts)
        decision_time = revocation_ts - timedelta(seconds=1)

        decision = check_execution(
            [planned_receipt, approval_receipt, revocation_receipt],
            action_digest=planned_receipt.event_digest,
            decision_time=decision_time,
        )

        assert decision.allowed is True
        assert decision.approval_digest == approval_receipt.digest

    def test_execution_blocked_by_expired_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 execution is blocked when approval has expired."""
        # Create workflow with TTL
        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,
            ttl="PT1H",  # 1 hour TTL
        )

        # Check execution 2 hours after approval
        approval_ts = datetime.fromisoformat(approval_receipt.event.ts)
        decision_time = approval_ts + timedelta(hours=2)

        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
            decision_time=decision_time,
        )

        assert decision.allowed is False
        assert decision.blocking_reason == BlockingReason.EXPIRED

    def test_execution_allowed_before_approval_expires(
        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 execution is allowed before TTL expires."""
        # Create workflow with TTL
        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,
            ttl="PT1H",  # 1 hour TTL
        )

        # Check execution 30 minutes after approval (before expiry)
        approval_ts = datetime.fromisoformat(approval_receipt.event.ts)
        decision_time = approval_ts + timedelta(minutes=30)

        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
            decision_time=decision_time,
        )

        assert decision.allowed is True


class TestBindingValidation:
    """Tests for binding validation in execution checks."""

    def test_execution_blocked_on_binding_mismatch(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that execution is blocked when bindings don't match."""
        # Create action with binding
        action_intent = Intent(
            kind=IntentKind.ACTION_PLANNED,
            action="invoice.send",
            category="financial",
            scope={"amount": 1000},
        )
        action_binding = Binding(
            artifacts=(
                ArtifactBinding(
                    name="invoice.pdf",
                    digest="a" * 64,
                    media_type="application/pdf",
                ),
            ),
        )
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=action_intent,
            binding=action_binding,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval with DIFFERENT binding digest
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_binding = Binding(
            artifacts=(
                ArtifactBinding(
                    name="invoice.pdf",
                    digest="b" * 64,  # Different!
                    media_type="application/pdf",
                ),
            ),
        )
        approval_intent = Intent(
            kind=IntentKind.APPROVAL_GRANTED,
            action="invoice.send",
            category="financial",
            scope={
                "purpose": "approval.before-send",
                "approvedReceiptDigest": planned_receipt.event_digest,
            },
        )
        approval_event = create_event(
            flow_id=sample_flow_id,
            actors=user_actors,
            intent=approval_intent,
            binding=approval_binding,
            chain=Chain(
                prev_receipt_digest=planned_receipt.event_digest,
                reason="Approval",
            ),
        )
        approval_receipt = create_receipt(
            event=approval_event,
            private_key=user_private_key,
            signer_did=user_did,
            role="user",
        )

        # Check execution - should be blocked due to binding mismatch
        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
        )

        assert decision.allowed is False
        assert decision.blocking_reason == BlockingReason.BINDING_MISMATCH

    def test_execution_allowed_with_matching_bindings(
        self,
        agent_private_key: EllipticCurvePrivateKey,
        user_private_key: EllipticCurvePrivateKey,
        sample_actors: Actors,
        sample_flow_id: str,
        agent_did: str,
        user_did: str,
    ) -> None:
        """Test that execution is allowed when bindings match."""
        # Create action with binding
        action_intent = Intent(
            kind=IntentKind.ACTION_PLANNED,
            action="invoice.send",
            category="financial",
            scope={"amount": 1000},
        )
        action_binding = Binding(
            artifacts=(
                ArtifactBinding(
                    name="invoice.pdf",
                    digest="a" * 64,
                    media_type="application/pdf",
                ),
            ),
        )
        event = create_event(
            flow_id=sample_flow_id,
            actors=sample_actors,
            intent=action_intent,
            binding=action_binding,
        )
        planned_receipt = create_receipt(
            event=event,
            private_key=agent_private_key,
            signer_did=agent_did,
        )

        # Create approval with SAME binding digest
        user_actors = Actors(issuer=user_did, subject=user_did)
        approval_binding = Binding(
            artifacts=(
                ArtifactBinding(
                    name="invoice.pdf",
                    digest="a" * 64,  # Same!
                    media_type="application/pdf",
                ),
            ),
        )
        approval_intent = Intent(
            kind=IntentKind.APPROVAL_GRANTED,
            action="invoice.send",
            category="financial",
            scope={
                "purpose": "approval.before-send",
                "approvedReceiptDigest": planned_receipt.event_digest,
            },
        )
        approval_event = create_event(
            flow_id=sample_flow_id,
            actors=user_actors,
            intent=approval_intent,
            binding=approval_binding,
            chain=Chain(
                prev_receipt_digest=planned_receipt.event_digest,
                reason="Approval",
            ),
        )
        approval_receipt = create_receipt(
            event=approval_event,
            private_key=user_private_key,
            signer_did=user_did,
            role="user",
        )

        # Check execution - should be allowed
        decision = check_execution(
            [planned_receipt, approval_receipt],
            action_digest=planned_receipt.event_digest,
        )

        assert decision.allowed is True
