from __future__ import annotations

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

from govoplan_core.core.poll import (
    PollAnswerRequest,
    PollInvitationRef,
    PollOptionRequest,
    PollResponseRef,
)


CAPABILITY_POLL_PARTICIPATION_GATEWAY = "poll.participation_gateway"
# Policy-attestation identifier, not a credential or credential default.
ANONYMOUS_PASSWORD_REQUIREMENT = "anonymous_password"  # nosec B105  # noqa: S105
PARTICIPATION_POLICY_VERSION = 1


def participation_token_fingerprint(token: str) -> str:
    """Return a non-reversible identifier suitable for audit/throttle keys."""

    return hashlib.sha256(token.encode("utf-8")).hexdigest()


@dataclass(frozen=True, slots=True)
class PollResponseGatewayRef:
    """Stable identity of the module resource governing a participation link."""

    module_id: str
    resource_type: str
    resource_id: str


@dataclass(frozen=True, slots=True)
class PollParticipationPolicy:
    """Generic response rules snapshotted onto one signed invitation.

    ``single_choice`` treats every non-``unavailable`` availability answer as
    a selection. Capacity is reserved only by ``available`` answers (and by
    selected answers for non-availability polls), so ``maybe`` never consumes
    a place.
    """

    version: int = PARTICIPATION_POLICY_VERSION
    single_choice: bool = False
    allow_maybe: bool = True
    max_participants_per_option: int | None = None
    allow_comments: bool = False
    participant_email_required: bool = False
    anonymous_password_required: bool = False


@dataclass(frozen=True, slots=True)
class PollGovernedInvitationCommand:
    gateway: PollResponseGatewayRef
    policy: PollParticipationPolicy
    respondent_id: str | None = None
    respondent_label: str | None = None
    email: str | None = None
    expires_at: datetime | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class PollGovernedResponseCommand:
    """Submission already authorized by the named in-process gateway.

    Passwords never cross this boundary. A gateway that owns a password
    verifier reports the completed check through ``verified_requirements``.
    Poll independently re-enforces the remaining snapshotted rules while
    holding its Poll-row lock.
    """

    respondent_id: str | None = None
    respondent_label: str | None = None
    participant_email: str | None = None
    participant_is_authenticated: bool = False
    answers: tuple[PollAnswerRequest, ...] = ()
    comment: str | None = None
    verified_requirements: frozenset[str] = frozenset()
    idempotency_key: str | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class PollGovernedResponseRef:
    response: PollResponseRef
    participant_email: str | None = None
    comment: str | None = None
    replayed: bool = False


@dataclass(frozen=True, slots=True)
class PollOptionMutationRef:
    id: str
    position: int
    replayed: bool = False
    invalidated_response_count: int = 0


@dataclass(frozen=True, slots=True)
class PollInvitationRevocationRef:
    id: str
    revoked_at: datetime
    replayed: bool = False


@dataclass(frozen=True, slots=True)
class PollInvitationExpiryRef:
    id: str
    expires_at: datetime | None
    replayed: bool = False


@dataclass(frozen=True, slots=True)
class PollParticipationContextRef:
    invitation_id: str
    tenant_id: str
    poll_id: str
    gateway: PollResponseGatewayRef
    policy: PollParticipationPolicy
    respondent_id: str | None = None
    respondent_label: str | None = None
    email: str | None = None
    response: PollGovernedResponseRef | None = None


@runtime_checkable
class PollParticipationGatewayProvider(Protocol):
    def create_governed_invitation(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        command: PollGovernedInvitationCommand,
    ) -> PollInvitationRef:
        ...

    def resolve_participation(
        self,
        session: object,
        *,
        token: str,
        gateway: PollResponseGatewayRef,
        respondent_id: str | None = None,
        participant_email: str | None = None,
        participant_is_authenticated: bool = False,
        verified_requirements: frozenset[str] = frozenset(),
    ) -> PollParticipationContextRef:
        """Resolve and prefill one valid invitation for the exact gateway."""

        ...

    def submit_governed_response(
        self,
        session: object,
        *,
        token: str,
        gateway: PollResponseGatewayRef,
        command: PollGovernedResponseCommand,
    ) -> PollGovernedResponseRef:
        ...

    def resolve_authenticated_participation(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        invitation_id: str,
        gateway: PollResponseGatewayRef,
        respondent_id: str,
    ) -> PollParticipationContextRef:
        """Resolve one governed invitation without retaining its public token."""

        ...

    def submit_authenticated_response(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        invitation_id: str,
        gateway: PollResponseGatewayRef,
        respondent_id: str,
        command: PollGovernedResponseCommand,
    ) -> PollGovernedResponseRef:
        """Submit atomically for the exact authenticated invitation identity."""

        ...

    def add_option(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        command: PollOptionRequest,
    ) -> PollOptionMutationRef:
        ...

    def remove_option(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        option_id: str,
    ) -> PollOptionMutationRef:
        ...

    def revoke_invitation(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        invitation_id: str,
    ) -> PollInvitationRevocationRef:
        ...

    def update_invitation_expiry(
        self,
        session: object,
        *,
        tenant_id: str,
        poll_id: str,
        invitation_id: str,
        gateway: PollResponseGatewayRef,
        expires_at: datetime | None,
    ) -> PollInvitationExpiryRef:
        """Expire or extend a non-revoked governed invitation in place."""

        ...


def poll_participation_gateway_provider(
    registry: object | None,
) -> PollParticipationGatewayProvider | None:
    """Resolve the governed Poll gateway without importing its implementation."""

    if registry is None or not hasattr(registry, "has_capability"):
        return None
    if not registry.has_capability(CAPABILITY_POLL_PARTICIPATION_GATEWAY):
        return None
    capability = registry.capability(CAPABILITY_POLL_PARTICIPATION_GATEWAY)
    return capability if isinstance(capability, PollParticipationGatewayProvider) else None


__all__ = [
    "ANONYMOUS_PASSWORD_REQUIREMENT",
    "CAPABILITY_POLL_PARTICIPATION_GATEWAY",
    "PARTICIPATION_POLICY_VERSION",
    "PollGovernedInvitationCommand",
    "PollGovernedResponseCommand",
    "PollGovernedResponseRef",
    "PollInvitationExpiryRef",
    "PollInvitationRevocationRef",
    "PollOptionMutationRef",
    "PollParticipationContextRef",
    "PollParticipationGatewayProvider",
    "PollParticipationPolicy",
    "PollResponseGatewayRef",
    "participation_token_fingerprint",
    "poll_participation_gateway_provider",
]
