from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from models.companion_rules import CompanionRulesSettings, CompanionRulesUpdate
from runtime.paths import user_data_dir
from services.runtime_rules import runtime_rules
from services.trading_stats import compute_zones_edit_lock_state

_SETTINGS_FILENAME = "settings.json"
_RULES_KEY = "rules"
_LEGACY_SESSION_KEY = "session"


def settings_file_path() -> Path:
    return user_data_dir() / _SETTINGS_FILENAME


def _read_all() -> dict[str, Any]:
    path = settings_file_path()
    if not path.is_file():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}
    return data if isinstance(data, dict) else {}


def _write_all(data: dict[str, Any]) -> None:
    path = settings_file_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")


def _state_to_settings() -> CompanionRulesSettings:
    s = runtime_rules.state
    return CompanionRulesSettings(
        discipline_engaged=s.discipline_engaged,
        discipline_engaged_at=(
            s.discipline_engaged_at.isoformat() if s.discipline_engaged_at else None
        ),
        session_enabled=s.session_enabled,
        session_timezone=s.session_timezone,
        session_start=s.session_start,
        session_end=s.session_end,
        max_trades_enabled=s.max_trades_enabled,
        max_trades_per_session=s.max_trades_per_session,
        max_risk_per_trade_enabled=s.max_risk_per_trade_enabled,
        max_risk_usd=s.max_risk_usd,
        daily_loss_limit_enabled=s.daily_loss_limit_enabled,
        max_daily_loss_pct=s.max_daily_loss_pct,
        paper_balance=s.paper_balance,
        max_lot_enabled=s.max_lot_enabled,
        max_quantity=s.max_quantity,
        trade_interval_enabled=s.trade_interval_enabled,
        trade_interval_mode=s.trade_interval_mode,
        trade_interval_minutes=s.trade_interval_minutes,
        trade_zones_enabled=s.trade_zones_enabled,
        trade_zones=s.trade_zones,
        zones_edit_lock_minutes=s.zones_edit_lock_minutes,
        usage_start=s.usage_start,
        usage_end=s.usage_end,
        exit_discipline_enabled=s.exit_discipline_enabled,
        exit_mode=s.exit_mode,
        sl_modify_limit_enabled=s.sl_modify_limit_enabled,
        sl_modify_max_count=s.sl_modify_max_count,
        sl_breakeven_only_enabled=s.sl_breakeven_only_enabled,
    )


def load_companion_rules() -> CompanionRulesSettings | None:
    data = _read_all()
    raw = data.get(_RULES_KEY)
    if not isinstance(raw, dict):
        raw = data.get(_LEGACY_SESSION_KEY)
    if not isinstance(raw, dict):
        return None
    try:
        return CompanionRulesSettings.model_validate(raw)
    except Exception:
        return None


def save_companion_rules(settings: CompanionRulesSettings) -> CompanionRulesSettings:
    data = _read_all()
    data[_RULES_KEY] = settings.model_dump()
    if _LEGACY_SESSION_KEY in data:
        del data[_LEGACY_SESSION_KEY]
    _write_all(data)
    _sync_session_env_defaults(settings)
    return settings


def _sync_session_env_defaults(settings: CompanionRulesSettings) -> None:
    """Keep user .env SESSION_* in sync so restarts never fall back to 09:00-18:00."""
    try:
        from runtime.paths import user_data_dir

        env_path = user_data_dir() / ".env"
        if not env_path.is_file():
            return
        text = env_path.read_text(encoding="utf-8")
        replacements = {
            "SESSION_ENABLED": "true" if settings.session_enabled else "false",
            "SESSION_TIMEZONE": settings.session_timezone,
            "SESSION_START": settings.session_start,
            "SESSION_END": settings.session_end,
        }
        lines = text.splitlines()
        seen: set[str] = set()
        out: list[str] = []
        for line in lines:
            stripped = line.strip()
            if not stripped or stripped.startswith("#") or "=" not in stripped:
                out.append(line)
                continue
            key = stripped.split("=", 1)[0].strip()
            if key in replacements:
                out.append(f"{key}={replacements[key]}")
                seen.add(key)
            else:
                out.append(line)
        for key, value in replacements.items():
            if key not in seen:
                out.append(f"{key}={value}")
        env_path.write_text("\n".join(out) + "\n", encoding="utf-8")
    except Exception:
        # Persistence of settings.json is authoritative; .env sync is best-effort.
        pass


def companion_rules_response() -> dict[str, Any]:
    settings = _state_to_settings()
    s = runtime_rules.state
    risk_pct = (
        round(s.max_risk_usd / s.paper_balance * 100.0, 2)
        if s.paper_balance > 0
        else 0.0
    )
    return {
        **settings.model_dump(),
        "max_risk_per_trade_pct": risk_pct,
        "rules_source": s.updated_from,
        "rules_updated_at": (
            runtime_rules.state.updated_at.isoformat()
            if runtime_rules.state.updated_at
            else None
        ),
        "persisted": load_companion_rules() is not None,
    }


def apply_companion_rules(
    update: CompanionRulesUpdate,
    *,
    persist: bool = True,
    skip_zone_edit_lock: bool = False,
) -> dict[str, Any]:
    if update.trade_zones is not None and not skip_zone_edit_lock:
        lock = compute_zones_edit_lock_state()
        if lock["locked"]:
            raise ValueError(
                "Drawing locked — cannot edit during the discipline session."
            )
    runtime_rules.apply_companion_rules(update)
    if persist:
        save_companion_rules(_state_to_settings())
    return companion_rules_response()


def load_persisted_rules_into_runtime() -> None:
    saved = load_companion_rules()
    if saved is None:
        return
    payload = saved.model_dump()
    engaged_at_raw = payload.pop("discipline_engaged_at", None)
    runtime_rules.apply_companion_rules(
        CompanionRulesUpdate.model_validate(payload),
        persist=False,
    )
    if engaged_at_raw:
        runtime_rules.state.discipline_engaged_at = datetime.fromisoformat(
            engaged_at_raw
        )
    elif saved.discipline_engaged:
        runtime_rules.state.discipline_engaged_at = datetime.now(timezone.utc)
    _migrate_persisted_rules_file_if_needed()
    # Meme si l'appli etait fermee : expire a usage_end ou au nouveau jour.
    from services.trading_stats import auto_release_discipline_if_needed

    auto_release_discipline_if_needed()


def _migrate_persisted_rules_file_if_needed() -> None:
    """Ajoute les cles discipline manquantes dans settings.json (anciennes versions)."""
    data = _read_all()
    raw = data.get(_RULES_KEY)
    if not isinstance(raw, dict):
        return
    required = (
        "max_lot_enabled",
        "max_quantity",
        "trade_interval_enabled",
        "trade_interval_mode",
        "trade_interval_minutes",
        "trade_zones_enabled",
        "trade_zones",
        "zones_edit_lock_minutes",
        "usage_start",
        "usage_end",
        "exit_discipline_enabled",
        "exit_mode",
        "sl_modify_limit_enabled",
        "sl_modify_max_count",
        "sl_breakeven_only_enabled",
    )
    needs_save = not all(key in raw for key in required)
    if needs_save:
        save_companion_rules(_state_to_settings())


# Aliases legacy session API
def session_settings_response() -> dict[str, Any]:
    return companion_rules_response()


def apply_session_settings(update: CompanionRulesUpdate, *, persist: bool = True) -> dict[str, Any]:
    return apply_companion_rules(update, persist=persist)


def load_persisted_session_into_runtime() -> None:
    load_persisted_rules_into_runtime()
