"""
User Event Types for Container → Gateway communication.

These types mirror the protobuf definitions in protobuf/user_events.proto
and the TypeScript types in gateway/src/events/types.ts.

Message Type IDs (must match protocol.md):
- UserEvent: 0x20
- EventAck: 0x21
"""

import json
import uuid
import time
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List, Optional, Any

# =============================================================================
# Message Type IDs
# =============================================================================

MSG_TYPE_USER_EVENT = 0x20
MSG_TYPE_EVENT_ACK = 0x21

# =============================================================================
# Enums
# =============================================================================


class EventType(IntEnum):
    """Types of events that containers can emit."""

    # Trading events
    ORDER_PLACED = 0
    ORDER_FILLED = 1
    ORDER_CANCELLED = 2
    ORDER_REJECTED = 3
    ORDER_EXPIRED = 4

    # Alert events
    ALERT_TRIGGERED = 10
    ALERT_CREATED = 11
    ALERT_DELETED = 12

    # Position events
    POSITION_OPENED = 20
    POSITION_CLOSED = 21
    POSITION_UPDATED = 22
    POSITION_LIQUIDATED = 23

    # Workspace/chart events
    WORKSPACE_CHANGED = 30
    CHART_ANNOTATION_ADDED = 31
    CHART_ANNOTATION_REMOVED = 32
    INDICATOR_UPDATED = 33

    # Strategy events
    STRATEGY_STARTED = 40
    STRATEGY_STOPPED = 41
    STRATEGY_LOG = 42
    STRATEGY_ERROR = 43
    BACKTEST_COMPLETED = 44

    # System events
    CONTAINER_STARTING = 50
    CONTAINER_READY = 51
    CONTAINER_SHUTTING_DOWN = 52
    EVENT_ERROR = 53


class Priority(IntEnum):
    """
    Event delivery priority.

    INFORMATIONAL: Drop if no active session (fire-and-forget via XPUB)
    NORMAL: Best effort - XPUB if subscribed, else DEALER
    CRITICAL: Must deliver - always uses DEALER with ack
    """

    INFORMATIONAL = 0
    NORMAL = 1
    CRITICAL = 2


class ChannelType(IntEnum):
    """Delivery channel types."""

    ACTIVE_SESSION = 0  # Whatever's currently connected
    WEB = 1  # WebSocket to web UI
    TELEGRAM = 2  # Telegram bot message
    EMAIL = 3  # Email notification
    PUSH = 4  # Mobile push notification
    DISCORD = 5  # Discord webhook
    SLACK = 6  # Slack webhook


class AckStatus(IntEnum):
    """Event acknowledgment status."""

    DELIVERED = 0  # Successfully delivered to at least one channel
    QUEUED = 1  # Accepted and queued for delivery
    ACK_ERROR = 2  # Permanent failure - all channels failed


# =============================================================================
# Message Types
# =============================================================================


@dataclass
class ChannelPreference:
    """
    Channel delivery preference.

    Args:
        channel: Which channel to deliver to
        only_if_active: If True, skip this channel if user is not connected
    """

    channel: ChannelType
    only_if_active: bool = False

    def to_dict(self) -> dict:
        return {
            "channel": int(self.channel),
            "only_if_active": self.only_if_active,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "ChannelPreference":
        return cls(
            channel=ChannelType(data["channel"]),
            only_if_active=data.get("only_if_active", False),
        )


@dataclass
class DeliverySpec:
    """
    Delivery specification for an event.

    Args:
        priority: Delivery priority (INFORMATIONAL, NORMAL, CRITICAL)
        channels: Ordered list of channel preferences to try
    """

    priority: Priority = Priority.NORMAL
    channels: List[ChannelPreference] = field(default_factory=list)

    def to_dict(self) -> dict:
        return {
            "priority": int(self.priority),
            "channels": [c.to_dict() for c in self.channels],
        }

    @classmethod
    def from_dict(cls, data: dict) -> "DeliverySpec":
        return cls(
            priority=Priority(data.get("priority", Priority.NORMAL)),
            channels=[
                ChannelPreference.from_dict(c) for c in data.get("channels", [])
            ],
        )

    # -------------------------------------------------------------------------
    # Convenience constructors
    # -------------------------------------------------------------------------

    @staticmethod
    def informational() -> "DeliverySpec":
        """
        Drop if no active session.
        Use for: indicator updates, chart syncs, strategy logs when watching.
        """
        return DeliverySpec(
            priority=Priority.INFORMATIONAL,
            channels=[ChannelPreference(ChannelType.ACTIVE_SESSION, only_if_active=True)],
        )

    @staticmethod
    def active_or_telegram() -> "DeliverySpec":
        """
        Active session preferred, fallback to Telegram.
        Use for: alerts, position updates.
        """
        return DeliverySpec(
            priority=Priority.NORMAL,
            channels=[
                ChannelPreference(ChannelType.ACTIVE_SESSION, only_if_active=True),
                ChannelPreference(ChannelType.TELEGRAM, only_if_active=False),
            ],
        )

    @staticmethod
    def active_or_push() -> "DeliverySpec":
        """
        Active session preferred, fallback to push notification.
        Use for: alerts, position updates on mobile.
        """
        return DeliverySpec(
            priority=Priority.NORMAL,
            channels=[
                ChannelPreference(ChannelType.ACTIVE_SESSION, only_if_active=True),
                ChannelPreference(ChannelType.PUSH, only_if_active=False),
            ],
        )

    @staticmethod
    def critical() -> "DeliverySpec":
        """
        Must deliver through any available channel.
        Use for: order fills, liquidations, critical errors.
        """
        return DeliverySpec(
            priority=Priority.CRITICAL,
            channels=[
                ChannelPreference(ChannelType.ACTIVE_SESSION, only_if_active=True),
                ChannelPreference(ChannelType.TELEGRAM, only_if_active=False),
                ChannelPreference(ChannelType.PUSH, only_if_active=False),
                ChannelPreference(ChannelType.EMAIL, only_if_active=False),
            ],
        )

    @staticmethod
    def telegram_only() -> "DeliverySpec":
        """
        Deliver only to Telegram, regardless of active session.
        Use for: scheduled reports, digest notifications.
        """
        return DeliverySpec(
            priority=Priority.NORMAL,
            channels=[
                ChannelPreference(ChannelType.TELEGRAM, only_if_active=False),
            ],
        )


@dataclass
class UserEvent:
    """
    Event emitted by user container to gateway.

    Args:
        user_id: User ID this event belongs to
        event_id: Unique event ID (UUID) for deduplication and ack
        timestamp: Unix milliseconds when event was generated
        event_type: Type of event
        payload: Event data (will be JSON encoded)
        delivery: Delivery specification
    """

    event_type: EventType
    payload: Any = None
    delivery: DeliverySpec = field(default_factory=DeliverySpec)
    user_id: str = ""
    event_id: str = ""
    timestamp: int = 0

    def __post_init__(self):
        if not self.event_id:
            self.event_id = str(uuid.uuid4())
        if not self.timestamp:
            self.timestamp = int(time.time() * 1000)

    def to_dict(self) -> dict:
        return {
            "user_id": self.user_id,
            "event_id": self.event_id,
            "timestamp": self.timestamp,
            "event_type": int(self.event_type),
            "payload": self.payload,
            "delivery": self.delivery.to_dict(),
        }

    @classmethod
    def from_dict(cls, data: dict) -> "UserEvent":
        return cls(
            user_id=data.get("user_id", ""),
            event_id=data.get("event_id", ""),
            timestamp=data.get("timestamp", 0),
            event_type=EventType(data["event_type"]),
            payload=data.get("payload"),
            delivery=DeliverySpec.from_dict(data.get("delivery", {})),
        )


@dataclass
class EventAck:
    """
    Acknowledgment from gateway for a critical event.

    Args:
        event_id: Event ID being acknowledged
        status: Delivery status
        error_message: Error message if status is ERROR
        delivered_via: Which channel successfully delivered (optional)
    """

    event_id: str
    status: AckStatus
    error_message: str = ""
    delivered_via: Optional[ChannelType] = None

    def to_dict(self) -> dict:
        return {
            "event_id": self.event_id,
            "status": int(self.status),
            "error_message": self.error_message,
            "delivered_via": int(self.delivered_via) if self.delivered_via is not None else None,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "EventAck":
        delivered_via = data.get("delivered_via")
        return cls(
            event_id=data["event_id"],
            status=AckStatus(data["status"]),
            error_message=data.get("error_message", ""),
            delivered_via=ChannelType(delivered_via) if delivered_via is not None else None,
        )


# =============================================================================
# Serialization
# =============================================================================


def serialize_user_event(event: UserEvent) -> bytes:
    """
    Serialize UserEvent to wire format.
    Format: [1 byte msg type][JSON payload]

    Note: In production, replace with proper protobuf serialization.
    """
    json_bytes = json.dumps(event.to_dict()).encode("utf-8")
    return bytes([MSG_TYPE_USER_EVENT]) + json_bytes


def deserialize_user_event(data: bytes) -> UserEvent:
    """Deserialize UserEvent from wire format."""
    if len(data) < 2:
        raise ValueError("Data too short")

    msg_type = data[0]
    if msg_type != MSG_TYPE_USER_EVENT:
        raise ValueError(f"Invalid message type: expected {MSG_TYPE_USER_EVENT}, got {msg_type}")

    json_str = data[1:].decode("utf-8")
    return UserEvent.from_dict(json.loads(json_str))


def serialize_event_ack(ack: EventAck) -> bytes:
    """Serialize EventAck to wire format."""
    json_bytes = json.dumps(ack.to_dict()).encode("utf-8")
    return bytes([MSG_TYPE_EVENT_ACK]) + json_bytes


def deserialize_event_ack(data: bytes) -> EventAck:
    """Deserialize EventAck from wire format."""
    if len(data) < 2:
        raise ValueError("Data too short")

    msg_type = data[0]
    if msg_type != MSG_TYPE_EVENT_ACK:
        raise ValueError(f"Invalid message type: expected {MSG_TYPE_EVENT_ACK}, got {msg_type}")

    json_str = data[1:].decode("utf-8")
    return EventAck.from_dict(json.loads(json_str))


# =============================================================================
# Helpers
# =============================================================================


def get_event_type_name(event_type: EventType) -> str:
    """Get human-readable event type name."""
    return event_type.name


def get_channel_type_name(channel_type: ChannelType) -> str:
    """Get human-readable channel type name."""
    return channel_type.name
