from __future__ import annotations

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


CAPABILITY_CALENDAR_SCHEDULING = "calendar.scheduling"
CAPABILITY_CALENDAR_OUTBOX = "calendar.outbox"
CAPABILITY_CALENDAR_INVITATIONS = "calendar.invitations"
CALENDAR_AVAILABILITY_READ_SCOPE = "calendar:availability:read"
CALENDAR_EVENT_WRITE_SCOPE = "calendar:event:write"


class CalendarCapabilityError(ValueError):
    """Stable error raised by Calendar capability implementations."""


@dataclass(frozen=True, slots=True)
class CalendarEventRequest:
    summary: str
    start_at: datetime
    calendar_id: str | None = None
    description: str | None = None
    location: str | None = None
    status: str = "CONFIRMED"
    transparency: str = "OPAQUE"
    classification: str = "PUBLIC"
    end_at: datetime | None = None
    timezone: str | None = None
    attendees: tuple[Mapping[str, object], ...] = ()
    categories: tuple[str, ...] = ()
    related_to: tuple[Mapping[str, object], ...] = ()
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class CalendarEventRef:
    id: str
    calendar_id: str
    uid: str
    external_state: str = "local"
    outbox_operation_id: str | None = None


@dataclass(frozen=True, slots=True)
class CalendarInvitationAttendeeRequest:
    address: str
    name: str | None = None
    role: str = "REQ-PARTICIPANT"
    participation_status: str = "NEEDS-ACTION"
    rsvp: bool = True


@dataclass(frozen=True, slots=True)
class CalendarInvitationRequest:
    correlation_id: str
    source_module: str
    source_resource_type: str
    source_resource_id: str | None
    summary: str
    start_at: datetime
    attendees: tuple[CalendarInvitationAttendeeRequest, ...]
    calendar_id: str | None = None
    description: str | None = None
    location: str | None = None
    end_at: datetime | None = None
    timezone: str | None = None
    organizer: Mapping[str, object] | None = None
    classification: str = "PUBLIC"
    categories: tuple[str, ...] = ()
    metadata: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class CalendarInvitationRef:
    event_id: str
    calendar_id: str
    uid: str
    correlation_id: str
    source_module: str
    source_resource_type: str
    source_resource_id: str | None
    attendees: tuple[Mapping[str, object], ...] = ()
    external_state: str = "local"
    outbox_operation_id: str | None = None
    reply_ingress: str = "capability"
    recurrence_supported: bool = False
    degraded_reasons: tuple[str, ...] = ()


@runtime_checkable
class CalendarSchedulingProvider(Protocol):
    def list_freebusy(
        self,
        session: object,
        *,
        tenant_id: str,
        start_at: datetime,
        end_at: datetime,
        calendar_ids: Sequence[str] | None = None,
    ) -> Sequence[Mapping[str, object]]:
        ...

    def create_event(
        self,
        session: object,
        *,
        tenant_id: str,
        user_id: str | None,
        request: CalendarEventRequest,
    ) -> CalendarEventRef:
        ...


@runtime_checkable
class CalendarOutboxProvider(Protocol):
    """Stable worker boundary for Calendar-owned external side effects."""

    def dispatch_due(
        self,
        session: object,
        *,
        tenant_id: str | None = None,
        limit: int = 50,
    ) -> Mapping[str, object]:
        ...


@runtime_checkable
class CalendarInvitationProvider(Protocol):
    """Correlation-aware invitation boundary for Campaign and Mail adapters."""

    def upsert_invitation(
        self,
        session: object,
        *,
        tenant_id: str,
        user_id: str | None,
        request: CalendarInvitationRequest,
    ) -> CalendarInvitationRef:
        ...

    def get_invitation(
        self,
        session: object,
        *,
        tenant_id: str,
        correlation_id: str,
    ) -> CalendarInvitationRef | None:
        ...

    def record_response(
        self,
        session: object,
        *,
        tenant_id: str,
        attendee_address: str,
        participation_status: str,
        correlation_id: str | None = None,
        uid: str | None = None,
        responded_at: datetime | None = None,
        evidence: Mapping[str, object] | None = None,
    ) -> CalendarInvitationRef:
        ...

def calendar_scheduling_provider(registry: object | None) -> CalendarSchedulingProvider | None:
    if registry is None or not hasattr(registry, "has_capability"):
        return None
    if not registry.has_capability(CAPABILITY_CALENDAR_SCHEDULING):
        return None
    capability = registry.capability(CAPABILITY_CALENDAR_SCHEDULING)
    return capability if isinstance(capability, CalendarSchedulingProvider) else None


def calendar_outbox_provider(registry: object | None) -> CalendarOutboxProvider | None:
    if registry is None or not hasattr(registry, "has_capability"):
        return None
    if not registry.has_capability(CAPABILITY_CALENDAR_OUTBOX):
        return None
    capability = registry.capability(CAPABILITY_CALENDAR_OUTBOX)
    return capability if isinstance(capability, CalendarOutboxProvider) else None


def calendar_invitation_provider(
    registry: object | None,
) -> CalendarInvitationProvider | None:
    if registry is None or not hasattr(registry, "has_capability"):
        return None
    if not registry.has_capability(CAPABILITY_CALENDAR_INVITATIONS):
        return None
    capability = registry.capability(CAPABILITY_CALENDAR_INVITATIONS)
    return capability if isinstance(capability, CalendarInvitationProvider) else None
