"""Market structure snapshot — compute local structure and persist LLM-interpreted results."""

import json
import time
from pathlib import Path
from typing import Any, Optional

import config_manager

_MEMORY_PATH = config_manager.get_data_dir() / "market_structure_memory.json"
_SNAPSHOT_PATH = config_manager.get_data_dir() / "market_structure_snapshot.json"


def _ema(values: list[float], period: int) -> list[float]:
    if len(values) < period:
        return [0.0] * len(values)
    alpha = 2.0 / (period + 1.0)
    result = [0.0] * (period - 1)
    result.append(sum(values[:period]) / period)
    for v in values[period:]:
        result.append(v * alpha + result[-1] * (1.0 - alpha))
    return result


def _rolling_high(values: list[float], window: int) -> list[float]:
    result = [0.0] * len(values)
    for i in range(window, len(values) + 1):
        result[i - 1] = max(values[i - window : i])
    return result


def _rolling_low(values: list[float], window: int) -> list[float]:
    result = [0.0] * len(values)
    for i in range(window, len(values) + 1):
        result[i - 1] = min(values[i - window : i])
    return result


def compute_local_structure(
    m5_bars: list[dict],
    h1_bars: list[dict],
    macro_data: dict | None = None,
) -> dict[str, Any]:
    """Compute rule-based market structure from bars."""
    result: dict[str, Any] = {
        "market_structure": "UNCERTAIN",
        "dominant_direction": "NEUTRAL",
        "buy_multiplier": 1.0,
        "sell_multiplier": 1.0,
        "volatility_state": "NORMAL",
        "adx": 25.0,
        "regime_subtype": "STRUCTURE_UNCLEAR",
        "source": "local_structure_rule",
    }

    if not m5_bars or len(m5_bars) < 10:
        return result

    closes = [float(b.get("close", 0)) for b in m5_bars]
    highs = [float(b.get("high", 0)) for b in m5_bars]
    lows = [float(b.get("low", 0)) for b in m5_bars]
    current = closes[-1] if closes else 0

    ema9 = _ema(closes, 9)[-1]
    ema21 = _ema(closes, 21)[-1]

    if ema9 > ema21:
        result["dominant_direction"] = "BULLISH"
        result["buy_multiplier"] = 1.0
        result["sell_multiplier"] = 0.7
    elif ema9 < ema21:
        result["dominant_direction"] = "BEARISH"
        result["buy_multiplier"] = 0.7
        result["sell_multiplier"] = 1.0
    else:
        result["buy_multiplier"] = 0.85
        result["sell_multiplier"] = 0.85

    rolling_high_20 = _rolling_high(highs, 20)
    rolling_low_20 = _rolling_low(lows, 20)
    if rolling_high_20[-1] > 0 and rolling_low_20[-1] > 0:
        range_pct = (rolling_high_20[-1] - rolling_low_20[-1]) / current
        if range_pct < 0.005:
            result["volatility_state"] = "LOW"
            result["regime_subtype"] = "TIGHT_RANGE"
        elif range_pct > 0.015:
            result["volatility_state"] = "HIGH"

    if len(highs) >= 20 and len(lows) >= 20 and len(rolling_high_20) >= 20 and len(rolling_low_20) >= 20:
        last_5_closes = closes[-5:]
        # Bullish structure: HH + HL (higher highs + higher lows)
        higher_highs = highs[-1] > rolling_high_20[-20]
        higher_lows = lows[-1] > rolling_low_20[-20]
        # Bearish structure: LL + LH (lower lows + lower highs)
        lower_lows = lows[-1] < rolling_low_20[-20]
        lower_highs = highs[-1] < rolling_high_20[-20]

        if higher_highs and higher_lows:
            result["market_structure"] = "BULLISH_TREND"
            result["dominant_direction"] = "BULLISH"
        elif lower_lows and lower_highs:
            result["market_structure"] = "BEARISH_TREND"
            result["dominant_direction"] = "BEARISH"
        elif higher_highs or higher_lows:
            result["market_structure"] = "WEAK_BULLISH"
            result["dominant_direction"] = "SLIGHTLY_BULLISH"
        elif lower_lows or lower_highs:
            result["market_structure"] = "WEAK_BEARISH"
            result["dominant_direction"] = "SLIGHTLY_BEARISH"
        else:
            result["market_structure"] = "RANGING"
            result["dominant_direction"] = "NEUTRAL"

    if macro_data and isinstance(macro_data, dict):
        result["macro_direction"] = macro_data.get("direction", "NEUTRAL")
        result["macro_confidence"] = macro_data.get("confidence", 0.5)

    return result


def write_structure_snapshot(payload: dict[str, Any]) -> None:
    _SNAPSHOT_PATH.parent.mkdir(parents=True, exist_ok=True)
    tmp = _SNAPSHOT_PATH.with_name(f".market_structure_snapshot.{id(payload)}.tmp")
    tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    import os
    os.replace(tmp, _SNAPSHOT_PATH)


def merge_structure_snapshot(
    llm_data: dict[str, Any], local_snapshot: dict[str, Any]
) -> dict[str, Any]:
    merged = dict(local_snapshot)
    priority_keys = (
        "view", "market_structure", "dominant_direction", "shock_direction",
        "pullback_phase", "pullback_depth_bucket", "pullback_quality",
        "confirmation_state", "retrace_ratio", "session_name",
        "session_execution_bias", "regime_subtype", "volatility_state",
        "liquidity_state", "continuation_bias", "entry_timing_hint",
        "llm_confidence", "counter_trend_risk", "execution_advice",
        "buy_multiplier", "sell_multiplier", "reason",
    )
    for key in priority_keys:
        if key in llm_data and llm_data[key] is not None:
            merged[key] = llm_data[key]
    return merged


def get_structure_memory_context(max_entries: int = 5) -> list[dict[str, Any]]:
    if not _MEMORY_PATH.exists():
        return []
    try:
        entries = json.loads(_MEMORY_PATH.read_text(encoding="utf-8"))
        if not isinstance(entries, list):
            return []
        return entries[-max_entries:]
    except Exception:
        return []


def load_structure_snapshot(valid_only: bool = False) -> Optional[dict[str, Any]]:
    if not _SNAPSHOT_PATH.exists():
        return None
    try:
        snapshot = json.loads(_SNAPSHOT_PATH.read_text(encoding="utf-8"))
        if valid_only:
            valid_until = float(snapshot.get("valid_until", 0) or 0)
            if time.time() > valid_until:
                return None
        return snapshot
    except Exception:
        return None
