"""事件 payload、可见性、投递与可靠性契约清单。"""

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ValidationError

import app.schemas.event as event_schemas
from app.schemas.mediaserver import WebhookEventInfo
from app.schemas.types import ChainEventType, EventType
from app.schemas.workflow import ActionContext


class EventDelivery(StrEnum):
    """描述事件当前的交付保证。"""

    EPHEMERAL = "ephemeral"
    DURABLE_REQUIRED = "durable_required"


class EventVisibility(StrEnum):
    """描述事件是否属于插件公开扩展面。"""

    HOST_ONLY = "host_only"
    PLUGIN_PUBLIC = "plugin_public"
    TARGET_PLUGIN = "target_plugin"


class EventErrorBehavior(StrEnum):
    """描述 handler 异常后的传播规则。"""

    ISOLATE = "isolate"
    STOP_CHAIN = "stop_chain"
    NOTIFY = "notify"


@dataclass(frozen=True, slots=True)
class EventContract:
    """冻结一个事件的 payload 与运行语义。"""

    event_name: str
    payload_model: type[BaseModel] | None
    payload_contract: str
    mode: str
    visibility: EventVisibility
    delivery: EventDelivery
    error_behavior: EventErrorBehavior
    ordering: str
    sensitive_fields: tuple[str, ...] = ()
    legacy_reason: str | None = None


_PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
    EventType.ConfigChanged: event_schemas.ConfigChangeEventData,
    EventType.PluginReload: event_schemas.PluginReloadEventData,
    EventType.PluginAction: event_schemas.PluginActionEventData,
    EventType.PluginTriggered: event_schemas.PluginTriggeredEventData,
    EventType.CommandExcute: event_schemas.CommandExecuteEventData,
    EventType.SiteDeleted: event_schemas.SiteEventData,
    EventType.SiteUpdated: event_schemas.SiteEventData,
    EventType.SiteRefreshed: event_schemas.SiteEventData,
    EventType.AgentTokensUsage: event_schemas.AgentTokensUsageEventData,
    EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData,
    EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData,
    EventType.SubscribeModified: event_schemas.SubscribeModifiedEventData,
    EventType.DownloadAdded: event_schemas.DownloadAddedEventData,
    EventType.TransferComplete: event_schemas.TransferResultEventData,
    EventType.TransferFailed: event_schemas.TransferResultEventData,
    EventType.SubtitleTransferComplete: event_schemas.TransferResultEventData,
    EventType.SubtitleTransferFailed: event_schemas.TransferResultEventData,
    EventType.AudioTransferComplete: event_schemas.TransferResultEventData,
    EventType.AudioTransferFailed: event_schemas.TransferResultEventData,
    EventType.HistoryDeleted: event_schemas.HistoryDeletedEventData,
    EventType.DownloadFileDeleted: event_schemas.DownloadFileDeletedEventData,
    EventType.DownloadDeleted: event_schemas.DownloadDeletedEventData,
    EventType.UserMessage: event_schemas.UserMessageEventData,
    EventType.WebhookMessage: WebhookEventInfo,
    EventType.NoticeMessage: event_schemas.NoticeMessageEventData,
    EventType.SubscribeComplete: event_schemas.SubscribeCompleteEventData,
    EventType.SystemError: event_schemas.SystemErrorEventData,
    EventType.MetadataScrape: event_schemas.MetadataScrapeEventData,
    EventType.ModuleReload: event_schemas.EmptyEventData,
    EventType.MessageAction: event_schemas.MessageActionEventData,
    EventType.WorkflowExecute: event_schemas.WorkflowExecuteEventData,
    ChainEventType.PluginDataReset: event_schemas.PluginDataResetEventData,
    ChainEventType.AuthVerification: event_schemas.AuthCredentials,
    ChainEventType.AuthIntercept: event_schemas.AuthInterceptCredentials,
    ChainEventType.CommandRegister: event_schemas.CommandRegisterEventData,
    ChainEventType.TransferRename: event_schemas.TransferRenameEventData,
    ChainEventType.TransferRenameBuild: event_schemas.TransferRenameBuildEventData,
    ChainEventType.TransferIntercept: event_schemas.TransferInterceptEventData,
    ChainEventType.TransferOverwriteCheck: event_schemas.TransferOverwriteCheckEventData,
    ChainEventType.ResourceSelection: event_schemas.ResourceSelectionEventData,
    ChainEventType.ResourceDownload: event_schemas.ResourceDownloadEventData,
    ChainEventType.DiscoverSource: event_schemas.DiscoverSourceEventData,
    ChainEventType.MediaRecognizeConvert: event_schemas.MediaRecognizeConvertEventData,
    ChainEventType.RecommendSource: event_schemas.RecommendSourceEventData,
    ChainEventType.StorageOperSelection: event_schemas.StorageOperSelectionEventData,
    ChainEventType.AgentLLMProvider: event_schemas.AgentLLMProviderEventData,
    ChainEventType.SubscribeEpisodesRefresh: event_schemas.SubscribeEpisodesRefreshEventData,
    ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckEventData,
    ChainEventType.NameRecognize: event_schemas.NameRecognizeEventData,
    ChainEventType.MusicNameRecognize: event_schemas.MusicNameRecognizeEventData,
    ChainEventType.MediaRecognize: event_schemas.MediaRecognizeEventData,
    ChainEventType.MusicMediaRecognize: event_schemas.MusicMediaRecognizeEventData,
    ChainEventType.WorkflowExecution: ActionContext,
}

_DURABLE_REQUIRED = {
    EventType.SubscribeAdded,
    EventType.SubscribeModified,
    EventType.SubscribeDeleted,
    EventType.DownloadAdded,
    EventType.TransferComplete,
    EventType.TransferFailed,
}
_TARGET_PLUGIN = {EventType.PluginAction, EventType.PluginTriggered}
_HOST_ONLY = {EventType.SystemError, EventType.ConfigChanged, EventType.ModuleReload}
_SENSITIVE_FIELDS = {
    ChainEventType.AuthVerification: ("password", "token", "mfa_code"),
    ChainEventType.AuthIntercept: ("token",),
    ChainEventType.AgentLLMProvider: ("api_key",),
}


def _build_contract(event_type: EventType | ChainEventType) -> EventContract:
    """按 enum 类别和首批 model 映射构造完整契约。"""
    payload_model = _PAYLOAD_MODELS.get(event_type)
    is_chain = isinstance(event_type, ChainEventType)
    visibility = EventVisibility.PLUGIN_PUBLIC
    if event_type in _TARGET_PLUGIN:
        visibility = EventVisibility.TARGET_PLUGIN
    elif event_type in _HOST_ONLY:
        visibility = EventVisibility.HOST_ONLY
    return EventContract(
        event_name=f"{event_type.__class__.__name__}.{event_type.name}",
        payload_model=payload_model,
        payload_contract=payload_model.__name__ if payload_model else "legacy_dict",
        mode="chain" if is_chain else "broadcast",
        visibility=visibility,
        delivery=(
            EventDelivery.DURABLE_REQUIRED
            if event_type in _DURABLE_REQUIRED
            else EventDelivery.EPHEMERAL
        ),
        error_behavior=(
            EventErrorBehavior.STOP_CHAIN if is_chain else EventErrorBehavior.NOTIFY
        ),
        ordering="priority_serial" if is_chain else "priority_queue",
        sensitive_fields=_SENSITIVE_FIELDS.get(event_type, ()),
        legacy_reason=(
            None
            if payload_model
            else "现有插件 payload 尚未收敛为稳定 model，保留原始 dict ABI"
        ),
    )


EVENT_CONTRACTS = {
    event_type: _build_contract(event_type)
    for event_type in (*tuple(EventType), *tuple(ChainEventType))
}


def normalize_event_type(
    event_type: EventType | ChainEventType | str,
) -> EventType | ChainEventType | str:
    """把旧 SDK 传入的已知字符串恢复为 enum，未知扩展值保持原样。"""
    if not isinstance(event_type, str):
        return event_type
    for enum_type in (EventType, ChainEventType):
        try:
            return enum_type(event_type)
        except ValueError:
            continue
    return event_type


def get_event_contract(
    event_type: EventType | ChainEventType | str,
) -> EventContract:
    """返回已登记事件的完整契约，兼容传入 enum value 字符串。"""
    normalized = normalize_event_type(event_type)
    if isinstance(normalized, str):
        raise KeyError(normalized)
    return EVENT_CONTRACTS[normalized]


def validate_event_payload(
    event_type: EventType | ChainEventType | str,
    payload: Any,
) -> tuple[str, ...]:
    """在发送边界诊断首批 typed payload，保持原对象和插件 dict 形状不变。"""
    try:
        model = get_event_contract(event_type).payload_model
    except KeyError:
        # 动态插件在旧 ABI 下可能使用宿主枚举之外的字符串事件。
        return ()
    if model is None or payload is None:
        return ()
    if isinstance(payload, model):
        return ()
    try:
        model.model_validate(payload)
    except (ValidationError, TypeError, ValueError) as error:
        return (str(error),)
    return ()
