"""
WebSocket configuration schemas from websockets.yaml.

These schemas provide typed access to WebSocket configuration
including channels, message types, and connection settings.

SST File: lightwave/schema/definitions/websockets.yaml
Database Model: None (config-driven, not DB-backed)

Usage:
    from lightwave.schema.pydantic.contracts.websockets.config import (
        WebSocketsConfigSchema,
        ChannelConfig,
    )

    # Load all WebSocket config
    config = WebSocketsConfigSchema.get_from_sst()
    print(config.connection.idle_timeout_seconds)  # 300

    # Get channel configuration
    chat_channel = config.channels["ai_chat"]
    print(chat_channel.allowed_roles)  # ["tenant_subscriber", ...]
"""

from pathlib import Path
from typing import Any, ClassVar, Literal

import yaml
from pydantic import Field

from lightwave.schema.pydantic.core.base import SSTSchema

# =============================================================================
# Connection Configuration
# =============================================================================


class ReconnectConfig(SSTSchema):
    """Reconnection settings."""

    attempts: int = Field(5, ge=1, description="Max reconnect attempts")
    delay_ms: int = Field(1000, ge=100, description="Initial delay in ms")
    delay_max_ms: int = Field(30000, ge=1000, description="Max delay in ms")
    delay_multiplier: float = Field(2.0, ge=1.0, description="Backoff multiplier")


class ConnectionConfig(SSTSchema):
    """
    WebSocket connection configuration.

    Defines timeouts, limits, and reconnection behavior.
    """

    # Timeouts
    handshake_timeout_seconds: int = Field(30, ge=5, description="Handshake timeout")
    idle_timeout_seconds: int = Field(300, ge=60, description="Idle timeout")
    ping_interval_seconds: int = Field(30, ge=10, description="Ping interval")
    pong_timeout_seconds: int = Field(10, ge=5, description="Pong timeout")

    # Limits
    max_message_size_bytes: int = Field(65536, ge=1024, description="Max message size")
    max_connections_per_user: int = Field(10, ge=1, description="Max connections per user")
    max_connections_per_tenant: int = Field(1000, ge=10, description="Max connections per tenant")

    # Reconnection
    reconnect_attempts: int = Field(5, ge=1, description="Max reconnect attempts")
    reconnect_delay_ms: int = Field(1000, ge=100, description="Initial reconnect delay")
    reconnect_delay_max_ms: int = Field(30000, ge=1000, description="Max reconnect delay")
    reconnect_delay_multiplier: float = Field(2.0, ge=1.0, description="Backoff multiplier")

    # Authentication
    auth_method: Literal["jwt", "session", "api_key"] = Field("jwt", description="Auth method")
    auth_header: str = Field("Authorization", description="Auth header name")
    auth_query_param: str = Field("token", description="Auth query param fallback")


# =============================================================================
# Channel Configuration
# =============================================================================


class ChannelConfig(SSTSchema):
    """
    WebSocket channel configuration.

    Defines a logical channel with its path, permissions, and message types.
    """

    name: str = Field(..., description="Channel display name")
    path: str = Field(..., description="Channel URL path")
    description: str = Field("", description="Channel description")
    auth_required: bool = Field(True, description="Whether authentication is required")
    allowed_roles: list[str] = Field(
        default_factory=list,
        description="Roles allowed to connect",
    )
    message_types: dict[str, list[str]] = Field(
        default_factory=dict,
        description="Inbound/outbound message types",
    )

    def can_access(self, user_role: str) -> bool:
        """Check if a role can access this channel."""
        if not self.auth_required:
            return True
        return user_role in self.allowed_roles

    def can_send(self, message_type: str) -> bool:
        """Check if message type can be sent inbound."""
        inbound = self.message_types.get("inbound", [])
        return message_type in inbound

    def can_receive(self, message_type: str) -> bool:
        """Check if message type can be received outbound."""
        outbound = self.message_types.get("outbound", [])
        return message_type in outbound


# =============================================================================
# Message Type Configuration
# =============================================================================


class MessageTypeConfig(SSTSchema):
    """
    Configuration for a specific message type.

    Defines direction, channel, and payload schema.
    """

    direction: Literal["inbound", "outbound", "both"] = Field(..., description="Message direction")
    channel: str = Field(..., description="Channel this message belongs to")
    description: str = Field("", description="Message description")
    payload: dict[str, Any] = Field(
        default_factory=dict,
        description="Payload schema definition",
    )


# =============================================================================
# Error Code Configuration
# =============================================================================


class ErrorCodeConfig(SSTSchema):
    """Error code definition."""

    code: str = Field(..., description="Error code")
    message: str = Field(..., description="Default error message")


# =============================================================================
# Root Configuration Schema
# =============================================================================


class WebSocketsConfigSchema(SSTSchema):
    """
    Complete WebSocket configuration from websockets.yaml.

    This is the root schema containing all WebSocket configuration.

    Example:
        config = WebSocketsConfigSchema.get_from_sst()
        print(config.connection.idle_timeout_seconds)
        print(config.channels["ai_chat"].path)
    """

    SST_KEY: ClassVar[str] = "websockets"  # Registry key in __index.yaml

    connection: ConnectionConfig
    channels: dict[str, ChannelConfig] = Field(
        default_factory=dict,
        description="Channel configurations",
    )
    message_types: dict[str, MessageTypeConfig] = Field(
        default_factory=dict,
        description="Message type definitions",
    )
    error_codes: dict[str, str] = Field(
        default_factory=dict,
        description="Error code definitions",
    )

    @classmethod
    def get_sst_path(cls) -> Path:
        """Get path to SST YAML file (from __index.yaml registry)."""
        from lightwave.schema.core.paths import get_sst_path

        return get_sst_path(cls.SST_KEY)

    @classmethod
    def load_sst_data(cls) -> dict[str, Any]:
        """Load SST YAML file."""
        sst_path = cls.get_sst_path()
        if not sst_path.exists():
            raise FileNotFoundError(f"SST file not found: {sst_path}")
        with open(sst_path) as f:
            return yaml.safe_load(f)

    @classmethod
    def get_from_sst(cls) -> "WebSocketsConfigSchema":
        """
        Load WebSocket configuration from SST YAML.

        Returns:
            WebSocketsConfigSchema instance with all config
        """
        data = cls.load_sst_data()

        # Remove _meta key before validation
        data.pop("_meta", None)

        # Parse connection config
        connection = ConnectionConfig(**data.get("connection", {}))

        # Parse channels
        channels_raw = data.get("channels", {})
        channels = {name: ChannelConfig(**cfg) for name, cfg in channels_raw.items()}

        # Parse message types
        msg_types_raw = data.get("message_types", {})
        message_types = {name: MessageTypeConfig(**cfg) for name, cfg in msg_types_raw.items()}

        # Error codes are simple strings
        error_codes = data.get("error_codes", {})

        return cls(
            connection=connection,
            channels=channels,
            message_types=message_types,
            error_codes=error_codes,
        )

    def get_channel(self, name: str) -> ChannelConfig | None:
        """Get channel configuration by name."""
        return self.channels.get(name)

    def get_channel_for_message(self, message_type: str) -> str | None:
        """Get channel name for a message type."""
        msg_config = self.message_types.get(message_type)
        return msg_config.channel if msg_config else None

    def get_error_message(self, code: str) -> str:
        """Get error message for an error code."""
        return self.error_codes.get(code, f"Unknown error: {code}")
