"""Sidecar configuration for the streaming sidecar.

Issue #110: Migrated from python/opendeviationbar/sidecar.py to pydantic-settings.
Issue #96 Task #6: OPENDEVIATIONBAR_MAX_PENDING_BARS backpressure control.
Issue #107: Watchdog reliability parameters.
Issue #109: Health check HTTP port.

Environment variables use OPENDEVIATIONBAR_STREAMING_ prefix (historical convention).
"""

from __future__ import annotations

from typing import Any

from pydantic import AliasChoices, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings.sources.providers.env import EnvSettingsSource


def _default_symbols_from_registry() -> list[str]:
    """Default to enabled symbols from registry when OPENDEVIATIONBAR_STREAMING_SYMBOLS is unset.

    symbols.toml is the SSoT — set enabled=true there to add a symbol to streaming.
    """
    from opendeviationbar.symbol_registry import get_registered_symbols

    return get_registered_symbols(enabled_only=True)


def _default_thresholds_from_registry() -> list[int]:
    """Derive unique sorted thresholds from enabled symbols in the registry.

    symbols.toml is the SSoT — each symbol's `thresholds` list drives what is streamed.
    """
    from opendeviationbar.symbol_registry import get_all_symbol_threshold_pairs

    pairs = get_all_symbol_threshold_pairs(enabled_only=True)
    return sorted({t for _, t in pairs}) if pairs else [250]


class _CsvEnvSource(EnvSettingsSource):
    """Env source that falls back to CSV splitting for list fields.

    pydantic-settings tries ``json.loads()`` first for complex types. Our
    env vars use comma-separated format (``BTCUSDT,ETHUSDT``), not JSON
    (``["BTCUSDT","ETHUSDT"]``). This source falls back to CSV when JSON
    parsing fails.
    """

    def decode_complex_value(
        self,
        field_name: str,
        field: Any,  # noqa: ANN401
        value: Any,  # noqa: ANN401
    ) -> Any:  # noqa: ANN401
        try:
            return super().decode_complex_value(field_name, field, value)
        except ValueError:
            # Fall back to CSV parsing for list fields
            if isinstance(value, str):
                return [s.strip() for s in value.split(",") if s.strip()]
            raise


class SidecarConfig(BaseSettings):
    """Configuration for the streaming sidecar.

    Environment Variables
    ---------------------
    OPENDEVIATIONBAR_STREAMING_SYMBOLS : str
        Comma-separated symbol list (default: "")
    OPENDEVIATIONBAR_STREAMING_THRESHOLDS : str
        Comma-separated threshold list (default: from registry, e.g., "250,500,750")
    OPENDEVIATIONBAR_STREAMING_MICROSTRUCTURE : bool
        Include microstructure features (default: True)
    OPENDEVIATIONBAR_STREAMING_GAP_FILL : bool
        Fill gaps on startup (default: True)
    OPENDEVIATIONBAR_STREAMING_VERBOSE : bool
        Verbose logging (default: False)
    OPENDEVIATIONBAR_STREAMING_TIMEOUT_MS : int
        WebSocket timeout in ms (default: 5000)
    OPENDEVIATIONBAR_STREAMING_WATCHDOG_TIMEOUT_S : int
        Watchdog dead-engine detection (default: 600)
    OPENDEVIATIONBAR_STREAMING_MAX_WATCHDOG_RESTARTS : int
        Max engine restarts before exit (default: 3)
    OPENDEVIATIONBAR_MAX_PENDING_BARS : int
        Backpressure bound (default: 10000)
    OPENDEVIATIONBAR_HEALTH_PORT : int
        Health check HTTP port, 0 to disable (default: 8081)
    """

    model_config = SettingsConfigDict(
        env_prefix="OPENDEVIATIONBAR_STREAMING_",
        case_sensitive=False,
        populate_by_name=True,
    )

    # SSoT: symbols.toml (enabled=true). Env var OPENDEVIATIONBAR_STREAMING_SYMBOLS overrides.
    symbols: list[str] = Field(default_factory=_default_symbols_from_registry)
    # SSoT: symbols.toml (thresholds field per symbol). Env var OPENDEVIATIONBAR_STREAMING_THRESHOLDS overrides.
    thresholds: list[int] = Field(default_factory=_default_thresholds_from_registry)
    # Alias: existing env var is OPENDEVIATIONBAR_STREAMING_MICROSTRUCTURE (no INCLUDE_)
    include_microstructure: bool = Field(
        True,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_STREAMING_MICROSTRUCTURE",
            "OPENDEVIATIONBAR_STREAMING_INCLUDE_MICROSTRUCTURE",
        ),
    )
    verbose: bool = False
    timeout_ms: int = 5000
    watchdog_timeout_s: int = 600
    max_watchdog_restarts: int = 3
    # Alias: existing env var is
    # OPENDEVIATIONBAR_MAX_PENDING_BARS (no STREAMING_ prefix)
    max_pending_bars: int = Field(
        10_000,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_MAX_PENDING_BARS",
            "OPENDEVIATIONBAR_STREAMING_MAX_PENDING_BARS",
        ),
    )
    # Alias: existing env var is OPENDEVIATIONBAR_HEALTH_PORT (no STREAMING_ prefix)
    health_port: int = Field(
        8081,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_HEALTH_PORT",
            "OPENDEVIATIONBAR_STREAMING_HEALTH_PORT",
        ),
    )
    lethe_alert_threshold: int = Field(
        1,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_LETHE_ALERT_THRESHOLD",
            "OPENDEVIATIONBAR_STREAMING_LETHE_ALERT_THRESHOLD",
        ),
    )
    argus_timeout_s: int = Field(
        1800,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_ARGUS_TIMEOUT_S",
            "OPENDEVIATIONBAR_STREAMING_ARGUS_TIMEOUT_S",
        ),
    )
    charon_interval_s: int = Field(
        300,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_CHARON_INTERVAL_S",
            "OPENDEVIATIONBAR_STREAMING_CHARON_INTERVAL_S",
        ),
    )
    sse_enabled: bool = Field(
        True,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_SSE_ENABLED",
            "OPENDEVIATIONBAR_STREAMING_SSE_ENABLED",
        ),
    )
    # Issue #214: Forming bar push interval (seconds). 0 to disable.
    forming_bar_interval_s: float = Field(
        1.0,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_FORMING_BAR_INTERVAL_S",
            "OPENDEVIATIONBAR_STREAMING_FORMING_BAR_INTERVAL_S",
        ),
    )
    # Issue #214: Skip broadcasting forming bars older than this (seconds).
    forming_bar_stale_s: float = Field(
        60.0,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_FORMING_BAR_STALE_S",
            "OPENDEVIATIONBAR_STREAMING_FORMING_BAR_STALE_S",
        ),
    )
    # Lethe re-alert threshold: re-alert after N more drops
    lethe_rearm_threshold: int = Field(
        100,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_LETHE_REARM_THRESHOLD",
            "OPENDEVIATIONBAR_STREAMING_LETHE_REARM_THRESHOLD",
        ),
    )
    # Argus liveness check interval (seconds) — NOT argus_timeout_s (silence threshold)
    argus_check_interval_s: float = Field(
        300.0,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_ARGUS_CHECK_INTERVAL_S",
            "OPENDEVIATIONBAR_STREAMING_ARGUS_CHECK_INTERVAL_S",
        ),
    )
    # Minimum seconds between watchdog restarts (cooldown)
    min_restart_interval_s: int = Field(
        30,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_MIN_RESTART_INTERVAL_S",
            "OPENDEVIATIONBAR_STREAMING_MIN_RESTART_INTERVAL_S",
        ),
    )
    # Issue #215: Checkpoint push interval (seconds). 0 to disable.
    checkpoint_push_interval_s: float = Field(
        300.0,
        validation_alias=AliasChoices(
            "OPENDEVIATIONBAR_CHECKPOINT_PUSH_INTERVAL_S",
            "OPENDEVIATIONBAR_STREAMING_CHECKPOINT_PUSH_INTERVAL_S",
        ),
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: Any,  # noqa: ANN401
        env_settings: Any,  # noqa: ANN401, ARG003
        dotenv_settings: Any,  # noqa: ANN401
        file_secret_settings: Any,  # noqa: ANN401
        **kwargs: Any,  # noqa: ANN401, ARG003
    ) -> tuple[Any, ...]:
        """Use CSV-aware env source for comma-separated list fields."""
        return (
            init_settings,
            _CsvEnvSource(settings_cls),
            dotenv_settings,
            file_secret_settings,
        )

    @classmethod
    def from_env(cls) -> SidecarConfig:
        """Create config from environment (backwards-compatible factory)."""
        return cls()
