"""Ouroboros: Reset boundary definitions for open deviation bars.

Named after the Greek serpent eating its tail (οὐροβόρος).

This module provides:
- OuroborosMode enum (AION only -- day mode removed)
- Boundary helpers (Aion returns no boundaries)
- Exchange session detection (Sydney/Tokyo/London/New York)
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, date, datetime
from enum import StrEnum
from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
    from collections.abc import Iterator

# ============================================================================
# Types
# ============================================================================


class OuroborosMode(StrEnum):
    """Ouroboros reset mode.

    AION runs continuously with no reset boundaries. Named after the Greek
    deity of unbounded time.
    """

    AION = "aion"


@dataclass(frozen=True)
class OuroborosBoundary:
    """A single ouroboros reset boundary."""

    timestamp: datetime
    """UTC datetime of the boundary."""

    mode: OuroborosMode
    """Which granularity created this boundary."""

    reason: str
    """Human-readable reason."""

    @property
    def timestamp_ms(self) -> int:
        """Timestamp in milliseconds (for comparison with trade data)."""
        return int(self.timestamp.timestamp() * 1000)

    @property
    def timestamp_us(self) -> int:
        """Timestamp in microseconds."""
        return int(self.timestamp.timestamp() * 1_000_000)


# ============================================================================
# Boundary Calculation
# ============================================================================


def get_ouroboros_boundaries(
    start: date,  # noqa: ARG001
    end: date,  # noqa: ARG001
    mode: Literal["aion"] = "aion",
) -> list[OuroborosBoundary]:
    """Return all ouroboros reset points within the date range.

    Parameters
    ----------
    start : date
        Start date (inclusive)
    end : date
        End date (inclusive)
    mode : {"aion"}
        Ouroboros granularity. Aion mode returns no boundaries (continuous).

    Returns
    -------
    list[OuroborosBoundary]
        Always empty for Aion mode.
    """
    validate_ouroboros_mode(mode)
    return []


def iter_ouroboros_segments(
    start: date,
    end: date,
    mode: Literal["aion"] = "aion",
) -> Iterator[tuple[datetime, datetime, OuroborosBoundary | None]]:
    """Iterate over date segments between ouroboros boundaries.

    Yields (segment_start, segment_end, boundary) tuples. Aion mode
    yields a single segment spanning the full range with no boundary.

    Parameters
    ----------
    start : date
        Start date
    end : date
        End date
    mode : {"aion"}
        Ouroboros granularity.

    Yields
    ------
    tuple[datetime, datetime, OuroborosBoundary | None]
        (segment_start, segment_end, boundary_at_start)
    """
    validate_ouroboros_mode(mode)

    # Convert dates to datetimes
    start_dt = datetime(start.year, start.month, start.day, 0, 0, 0, tzinfo=UTC)
    end_dt = datetime(end.year, end.month, end.day, 23, 59, 59, 999999, tzinfo=UTC)

    # Aion: single segment, no boundaries
    yield (start_dt, end_dt, None)


# ============================================================================
# Exchange Market Sessions
# ============================================================================

# Market session hours in local time (aligned with actual exchange hours)
# Note: These are approximate for crypto; traditional markets have pre/post sessions
# Issue #8: Exchange sessions integration (corrected per exchange schedules)
EXCHANGE_SESSION_HOURS: dict[str, dict[str, str | int]] = {
    "sydney": {"tz": "Australia/Sydney", "start": 10, "end": 16},  # ASX
    "tokyo": {"tz": "Asia/Tokyo", "start": 9, "end": 15},  # TSE
    "london": {"tz": "Europe/London", "start": 8, "end": 17},  # LSE
    "newyork": {"tz": "America/New_York", "start": 10, "end": 16},  # NYSE
}


@dataclass(frozen=True)
class ExchangeSessionFlags:
    """Boolean flags for active exchange market sessions."""

    sydney: bool
    tokyo: bool
    london: bool
    newyork: bool

    def to_dict(self) -> dict[str, bool]:
        """Convert to dict with column names."""
        return {
            "exchange_session_sydney": self.sydney,
            "exchange_session_tokyo": self.tokyo,
            "exchange_session_london": self.london,
            "exchange_session_newyork": self.newyork,
        }


def get_active_exchange_sessions(timestamp_utc: datetime) -> ExchangeSessionFlags:
    """Determine which exchange market sessions are active at a given UTC time.

    Parameters
    ----------
    timestamp_utc : datetime
        UTC datetime to check (must be timezone-aware)

    Returns
    -------
    ExchangeSessionFlags
        Boolean flags for each session

    Notes
    -----
    This is a simplified implementation that uses fixed hours.
    For production use with DST accuracy, consider using nautilus_trader's
    ForexSession implementation.
    """
    import zoneinfo

    def is_in_session(session_name: str) -> bool:
        info = EXCHANGE_SESSION_HOURS[session_name]
        tz = zoneinfo.ZoneInfo(str(info["tz"]))
        local_time = timestamp_utc.astimezone(tz)
        hour = local_time.hour
        # Skip weekends
        if local_time.weekday() >= 5:
            return False
        return int(info["start"]) <= hour < int(info["end"])

    return ExchangeSessionFlags(
        sydney=is_in_session("sydney"),
        tokyo=is_in_session("tokyo"),
        london=is_in_session("london"),
        newyork=is_in_session("newyork"),
    )


# ============================================================================
# Validation
# ============================================================================


def validate_ouroboros_mode(mode: str) -> Literal["aion"]:
    """Validate ouroboros mode string.

    Parameters
    ----------
    mode : str
        Mode to validate

    Returns
    -------
    Literal["aion"]
        Validated mode

    Raises
    ------
    ValueError
        If mode is not "aion"
    """
    valid = {"aion"}
    if mode not in valid:
        msg = f"Invalid ouroboros mode: {mode!r}. Valid modes: {sorted(valid)}"
        raise ValueError(msg)
    return "aion"


def get_operational_ouroboros_mode() -> Literal["aion"]:
    """Resolve the operational ouroboros mode from Settings.  # Issue #126

    Reads ``OPENDEVIATIONBAR_OUROBOROS_MODE`` via the unified Settings singleton
    and validates it. This is the single source of truth for resolving
    the default ouroboros mode at runtime.

    Returns
    -------
    Literal["aion"]
        The configured ouroboros mode.

    Raises
    ------
    ValueError
        If the configured mode is not valid.
    """
    from opendeviationbar.config import Settings

    return validate_ouroboros_mode(Settings.get().population.ouroboros_mode)
