from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

import yaml


@dataclass
class VenueCfg:
    rest_url: str = "https://mainnet.zklighter.elliot.ai"
    ws_url: str = "wss://mainnet.zklighter.elliot.ai/stream?readonly=true"
    symbol: str = "BTC"
    market_id: Optional[int] = None


@dataclass
class CapitalCfg:
    paper_equity_usd: float = 1000.0
    target_working_usd: float = 1000.0


@dataclass
class QuotingCfg:
    lot_usd_min: float = 10.0
    lot_usd_max: float = 25.0
    half_spread_bps: float = 2.0
    # Health-check cadence (NOT a wipe interval). Ladder rests between moves.
    requote_interval_ms: int = 5500
    inventory_skew_bps_per_btc: float = 80.0
    max_inventory_btc: float = 0.005
    post_only: bool = True
    # How often to poll account for inventory / fills (1 REST each).
    account_refresh_s: float = 12.0
    # Multi-level ladder (live). Max active orders/market on Lighter ≈ 30.
    n_bid_levels: int = 12
    n_ask_levels: int = 12
    level_spacing_bps: float = 0.5
    # Re-ladder only when mid moves at least this many bps (rest otherwise).
    rest_threshold_bps: float = 3.0
    # Prefer send_tx_batch for placing N orders in 1 REST call.
    batch_place: bool = True
    # Min seconds to leave ladder resting after a place (anti-thrash).
    min_rest_s: float = 40.0


@dataclass
class RiskCfg:
    kill_day_pnl_pct: float = -1.0
    max_fills_per_day: int = 12000
    soft_leverage_min: float = 5.0
    soft_leverage_max: float = 10.0
    max_notional_usd: float = 400.0


@dataclass
class PaperCfg:
    fill_on_touch: bool = True
    maker_fee_bps: float = 0.0
    taker_fee_bps: float = 0.0
    simulate_partial: bool = False
    near_fill_bps: float = 0.0  # demo: fill if mid within N bps of quote
    # Phase A3 mechanics (paper-only):
    # mid_cross = legacy toxic (fill when mid gaps through quote → edge≤0)
    # maker_touch = trade-through OR good-side touch (edge≥0 at fill)
    fills_mode: str = "mid_cross"
    maker_touch_bps: float = 0.8  # fill when mid within this of quote, still good-side
    maker_touch_min_age_s: float = 0.35  # quote must rest before touch fill
    hard_sl_bps: float = 0.0  # per-lot MTM stop; 0=off
    time_stop_s: float = 0.0  # per-lot max hold; 0=off
    mom_gate_bps: float = 0.0  # |ret| above this → suppress adverse side; 0=off
    post_fill_reduce_s: float = 0.0  # after fill, only quote reduce side this long
    post_fill_half_mult: float = 0.4  # tighten reduce-side half during reduce window
    exit_near_bps: float = 0.0  # when holding, fill reduce side if mid within this (no approach req)
    take_profit_bps: float = 0.0  # lock winner at mid when MTM >= this


@dataclass
class StatusCfg:
    path: str = "data/status.json"
    console_every_s: float = 2.0


@dataclass
class RunCfg:
    duration_s: float = 0.0


@dataclass
class RateLimitCfg:
    """Soft pace for Lighter REST (L1 address: 40/60s hard)."""
    max_per_window: int = 36
    window_s: float = 60.0
    on_429_backoff_s: float = 1.5


@dataclass
class AppConfig:
    mode: str = "paper"
    live_enabled: bool = False
    venue: VenueCfg = field(default_factory=VenueCfg)
    capital: CapitalCfg = field(default_factory=CapitalCfg)
    quoting: QuotingCfg = field(default_factory=QuotingCfg)
    risk: RiskCfg = field(default_factory=RiskCfg)
    paper: PaperCfg = field(default_factory=PaperCfg)
    status: StatusCfg = field(default_factory=StatusCfg)
    run: RunCfg = field(default_factory=RunCfg)
    rate_limit: RateLimitCfg = field(default_factory=RateLimitCfg)

    @property
    def is_live(self) -> bool:
        return (
            self.mode == "live"
            and self.live_enabled
            and os.environ.get("LIVE", "") == "1"
        )


def _merge(dc_cls, data: dict[str, Any] | None):
    data = data or {}
    known = {f.name for f in dc_cls.__dataclass_fields__.values()}  # type: ignore
    return dc_cls(**{k: v for k, v in data.items() if k in known})


def load_config(path: str | Path) -> AppConfig:
    path = Path(path)
    raw = yaml.safe_load(path.read_text()) or {}
    return AppConfig(
        mode=raw.get("mode", "paper"),
        live_enabled=bool(raw.get("live_enabled", False)),
        venue=_merge(VenueCfg, raw.get("venue")),
        capital=_merge(CapitalCfg, raw.get("capital")),
        quoting=_merge(QuotingCfg, raw.get("quoting")),
        risk=_merge(RiskCfg, raw.get("risk")),
        paper=_merge(PaperCfg, raw.get("paper")),
        status=_merge(StatusCfg, raw.get("status")),
        run=_merge(RunCfg, raw.get("run")),
        rate_limit=_merge(RateLimitCfg, raw.get("rate_limit")),
    )
