from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol, runtime_checkable


CAPABILITY_APPROVAL_REQUESTS = "approvals.requests"


class ApprovalCapabilityError(ValueError):
    """Stable error raised by Approval capability implementations."""


@dataclass(frozen=True, slots=True)
class ApprovalActorSelector:
    kind: str
    value: str
    label: str | None = None


@dataclass(frozen=True, slots=True)
class ApprovalStepDefinition:
    key: str
    label: str
    selectors: tuple[ApprovalActorSelector, ...]
    required_approvals: int = 1
    rejection_policy: str = "fail_fast"
    due_at: datetime | None = None
    signature_required: bool = False
    forbidden_evidence_roles: tuple[str, ...] = ()
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class ApprovalRequestCreateCommand:
    title: str
    subject_module: str
    subject_type: str
    subject_id: str
    subject_version: str | None
    subject_digest: str
    steps: tuple[ApprovalStepDefinition, ...]
    description: str | None = None
    separation_of_duties: bool = True
    unique_actors_across_steps: bool = False
    expires_at: datetime | None = None
    policy_refs: tuple[str, ...] = ()
    evidence_actors: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
    template_id: str | None = None
    template_revision: int | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class ApprovalTemplateCreateCommand:
    key: str
    title: str
    steps: tuple[ApprovalStepDefinition, ...]
    description: str | None = None
    separation_of_duties: bool = True
    unique_actors_across_steps: bool = False
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class ApprovalTemplateRef:
    id: str
    key: str
    revision: int
    state: str
    content_sha256: str


@dataclass(frozen=True, slots=True)
class ApprovalDecisionCommand:
    outcome: str
    reason: str
    expected_revision: int
    idempotency_key: str
    delegated_for_account_id: str | None = None
    signature_ref: Mapping[str, object] | None = None


@dataclass(frozen=True, slots=True)
class ApprovalRequestRef:
    id: str
    revision: int
    state: str
    current_step_key: str | None = None


@dataclass(frozen=True, slots=True)
class ApprovalDecisionReceipt:
    request_id: str
    revision: int
    state: str
    step_key: str
    outcome: str
    actor_id: str
    recorded_at: datetime
    receipt_sha256: str
    authority_provenance: Mapping[str, object] = field(default_factory=dict)
    replayed: bool = False


@dataclass(frozen=True, slots=True)
class ApprovalCheck:
    request_id: str
    revision: int
    state: str
    approved: bool
    subject_module: str
    subject_type: str
    subject_id: str
    subject_version: str | None
    subject_digest: str
    completed_at: datetime | None = None


@runtime_checkable
class ApprovalRequestProvider(Protocol):
    def create_template(
        self,
        session: object,
        principal: object,
        *,
        command: ApprovalTemplateCreateCommand,
        idempotency_key: str,
    ) -> ApprovalTemplateRef: ...

    def revise_template(
        self,
        session: object,
        principal: object,
        *,
        template_id: str,
        command: ApprovalTemplateCreateCommand,
        expected_revision: int,
        idempotency_key: str,
    ) -> ApprovalTemplateRef: ...

    def publish_template(
        self,
        session: object,
        principal: object,
        *,
        template_id: str,
        expected_revision: int,
        idempotency_key: str,
    ) -> ApprovalTemplateRef: ...

    def create_request(
        self,
        session: object,
        principal: object,
        *,
        command: ApprovalRequestCreateCommand,
        idempotency_key: str,
    ) -> ApprovalRequestRef: ...

    def get_request(
        self,
        session: object,
        principal: object,
        *,
        request_id: str,
    ) -> Mapping[str, object] | None: ...

    def decide(
        self,
        session: object,
        principal: object,
        *,
        request_id: str,
        command: ApprovalDecisionCommand,
    ) -> ApprovalDecisionReceipt: ...

    def check_approved(
        self,
        session: object,
        principal: object,
        *,
        request_id: str,
        subject_module: str,
        subject_type: str,
        subject_id: str,
        subject_version: str | None,
        subject_digest: str,
    ) -> ApprovalCheck: ...


__all__ = [
    "ApprovalActorSelector",
    "ApprovalCapabilityError",
    "ApprovalCheck",
    "ApprovalDecisionCommand",
    "ApprovalDecisionReceipt",
    "ApprovalRequestCreateCommand",
    "ApprovalRequestProvider",
    "ApprovalRequestRef",
    "ApprovalStepDefinition",
    "ApprovalTemplateCreateCommand",
    "ApprovalTemplateRef",
    "CAPABILITY_APPROVAL_REQUESTS",
]
