from dataclasses import dataclass
from datetime import datetime, timezone

from config import settings
from models.companion_rules import CompanionRulesUpdate, TradeZone
from models.discipline import PineConfigPayload


@dataclass
class RuntimeRulesState:
    discipline_engaged: bool
    discipline_engaged_at: datetime | None
    session_enabled: bool
    session_timezone: str
    session_start: str
    session_end: str
    max_trades_enabled: bool
    max_trades_per_session: int
    max_risk_per_trade_enabled: bool
    daily_loss_limit_enabled: bool
    max_open_positions: int
    max_daily_loss_pct: float
    max_quantity: float
    max_risk_usd: float
    max_lot_enabled: bool
    trade_interval_enabled: bool
    trade_interval_mode: str
    trade_interval_minutes: int
    paper_balance: float
    trade_zones_enabled: bool
    trade_zones: list[TradeZone]
    zones_edit_lock_minutes: int
    usage_start: str
    usage_end: str
    exit_discipline_enabled: bool
    exit_mode: str
    sl_modify_limit_enabled: bool
    sl_modify_max_count: int
    sl_breakeven_only_enabled: bool
    live_price: float | None
    live_price_instrument: str | None
    live_price_at: datetime | None
    pine_strategy: str | None = None
    updated_at: datetime | None = None
    updated_from: str = "env"


class RuntimeRules:
    """Règles actives — .env par défaut, écrasables par config Pine."""

    def __init__(self) -> None:
        self._state = self._from_settings()

    def _from_settings(self) -> RuntimeRulesState:
        return RuntimeRulesState(
            discipline_engaged=False,
            discipline_engaged_at=None,
            session_enabled=settings.session_enabled,
            session_timezone=settings.session_timezone,
            session_start=settings.session_start,
            session_end=settings.session_end,
            max_trades_enabled=True,
            max_trades_per_session=settings.max_trades_per_session,
            max_risk_per_trade_enabled=True,
            daily_loss_limit_enabled=True,
            max_open_positions=settings.max_open_positions,
            max_daily_loss_pct=settings.max_daily_loss_pct,
            max_quantity=settings.max_quantity,
            max_risk_usd=settings.max_risk_usd,
            max_lot_enabled=False,
            trade_interval_enabled=False,
            trade_interval_mode="after_loss",
            trade_interval_minutes=15,
            paper_balance=settings.paper_balance,
            trade_zones_enabled=False,
            trade_zones=[],
            zones_edit_lock_minutes=60,
            usage_start="09:00",
            usage_end="22:00",
            exit_discipline_enabled=False,
            exit_mode="manual_or_sl",
            sl_modify_limit_enabled=True,
            sl_modify_max_count=0,
            sl_breakeven_only_enabled=False,
            live_price=None,
            live_price_instrument=None,
            live_price_at=None,
            updated_from="env",
        )

    def reset_to_env(self) -> RuntimeRulesState:
        self._state = self._from_settings()
        return self._state

    def apply_pine_config(self, config: PineConfigPayload) -> RuntimeRulesState:
        s = self._state
        if config.session_start is not None:
            s.session_start = config.session_start
        if config.session_end is not None:
            s.session_end = config.session_end
        if config.session_timezone is not None:
            s.session_timezone = config.session_timezone
        if config.max_trades_per_session is not None:
            s.max_trades_per_session = config.max_trades_per_session
        if config.max_daily_loss_pct is not None:
            s.max_daily_loss_pct = config.max_daily_loss_pct
        if config.max_open_positions is not None:
            s.max_open_positions = config.max_open_positions
        if config.max_quantity is not None:
            s.max_quantity = config.max_quantity
        if config.strategy_name is not None:
            s.pine_strategy = config.strategy_name
        s.updated_at = datetime.now(timezone.utc)
        s.updated_from = "pine"
        return s

    def apply_companion_rules(
        self,
        update: CompanionRulesUpdate,
        *,
        persist: bool = True,
    ) -> RuntimeRulesState:
        del persist  # persistance geree par user_settings
        s = self._state
        if update.discipline_engaged is not None:
            s.discipline_engaged = update.discipline_engaged
            if update.discipline_engaged:
                s.discipline_engaged_at = datetime.now(timezone.utc)
            else:
                s.discipline_engaged_at = None
        if update.session_enabled is not None:
            s.session_enabled = update.session_enabled
        if update.session_start is not None:
            s.session_start = update.session_start
        if update.session_end is not None:
            s.session_end = update.session_end
        if update.session_timezone is not None:
            s.session_timezone = update.session_timezone
        if update.max_trades_enabled is not None:
            s.max_trades_enabled = update.max_trades_enabled
        if update.max_trades_per_session is not None:
            s.max_trades_per_session = update.max_trades_per_session
        if update.paper_balance is not None:
            s.paper_balance = update.paper_balance
        if update.max_risk_per_trade_enabled is not None:
            s.max_risk_per_trade_enabled = update.max_risk_per_trade_enabled
        if update.max_risk_usd is not None:
            s.max_risk_usd = update.max_risk_usd
        elif update.max_risk_per_trade_pct is not None and s.paper_balance > 0:
            s.max_risk_usd = round(
                s.paper_balance * update.max_risk_per_trade_pct / 100.0, 2
            )
        if update.daily_loss_limit_enabled is not None:
            s.daily_loss_limit_enabled = update.daily_loss_limit_enabled
        if update.max_daily_loss_pct is not None:
            s.max_daily_loss_pct = update.max_daily_loss_pct
        if update.max_lot_enabled is not None:
            s.max_lot_enabled = update.max_lot_enabled
        if update.max_quantity is not None:
            s.max_quantity = update.max_quantity
        if update.trade_interval_enabled is not None:
            s.trade_interval_enabled = update.trade_interval_enabled
        if update.trade_interval_mode is not None:
            s.trade_interval_mode = update.trade_interval_mode
        if update.trade_interval_minutes is not None:
            s.trade_interval_minutes = update.trade_interval_minutes
        if update.trade_zones_enabled is not None:
            s.trade_zones_enabled = update.trade_zones_enabled
        if update.trade_zones is not None:
            s.trade_zones = _normalize_trade_zones(update.trade_zones)
        if update.zones_edit_lock_minutes is not None:
            s.zones_edit_lock_minutes = update.zones_edit_lock_minutes
        if update.usage_start is not None:
            s.usage_start = update.usage_start
        if update.usage_end is not None:
            s.usage_end = update.usage_end
        if update.exit_discipline_enabled is not None:
            s.exit_discipline_enabled = update.exit_discipline_enabled
        if update.exit_mode is not None:
            s.exit_mode = update.exit_mode
        if update.sl_modify_limit_enabled is not None:
            s.sl_modify_limit_enabled = update.sl_modify_limit_enabled
        if update.sl_modify_max_count is not None:
            s.sl_modify_max_count = update.sl_modify_max_count
        if update.sl_breakeven_only_enabled is not None:
            s.sl_breakeven_only_enabled = update.sl_breakeven_only_enabled
        s.updated_at = datetime.now(timezone.utc)
        s.updated_from = "companion"
        return s

    def apply_companion_session(
        self,
        update: CompanionRulesUpdate,
        *,
        persist: bool = True,
    ) -> RuntimeRulesState:
        return self.apply_companion_rules(update, persist=persist)

    @property
    def state(self) -> RuntimeRulesState:
        return self._state


runtime_rules = RuntimeRules()


def _normalize_trade_zones(zones: list[TradeZone]) -> list[TradeZone]:
    normalized: list[TradeZone] = []
    for zone in zones:
        low, high = sorted((zone.low, zone.high))
        normalized.append(
            zone.model_copy(
                update={
                    "low": low,
                    "high": high,
                }
            )
        )
    return normalized


def set_live_price(instrument: str | None, price: float) -> None:
    s = runtime_rules.state
    s.live_price = price
    s.live_price_instrument = instrument.strip() if instrument else None
    s.live_price_at = datetime.now(timezone.utc)
