"""
=========================================
CV6 AI Trading OS — Real Technical Indicators
=========================================
TIER-1 (1B): computes REAL technical indicators from REAL OHLCV candles
fetched via the connected Angel One broker's getCandleData API — replacing
the seeded-random fabrication that previously fed the AI trade decision.

Correct formulas (the earlier audit flagged bugs in app/indicators/*):
  • RSI  — Wilder's smoothing over the full series (not first-window only)
  • ATR  — Wilder's smoothing of True Range
  • EMA  — standard exponential moving average
  • VWAP — session-anchored cumulative(typical×vol)/cumulative(vol)
  • Supertrend — stateful final-band ratchet with correct flip logic

Candles are cached per (symbol, interval) with a short TTL so a ~5-symbol
narrowed candidate set stays well inside the broker's historical-data rate
limit. When real candles cannot be fetched, `data_quality="unavailable"`
and callers MUST NOT trade on it (the autonomous engine skips such
candidates in REAL mode rather than fabricate).
"""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple

from loguru import logger

# Angel One getCandleData interval + how much history to pull for a stable
# 14-period Wilder RSI/ATR and a 10-period Supertrend.
_INTERVAL = "FIVE_MINUTE"
_LOOKBACK_MIN = 5 * 200          # ~200 five-minute candles
_CACHE_TTL = 60.0                # seconds


@dataclass
class Technicals:
    symbol:        str
    data_quality:  str = "unavailable"   # "real" | "unavailable"
    ltp:           float = 0.0
    rsi:           float = 0.0
    ema_9:         float = 0.0
    ema_21:        float = 0.0
    vwap:          float = 0.0
    atr:           float = 0.0
    atr_pct:       float = 0.0
    supertrend:    str = "NEUTRAL"        # UP | DOWN | NEUTRAL
    volume_ratio:  float = 0.0
    candles_used:  int = 0

    @property
    def is_real(self) -> bool:
        return self.data_quality == "real"


_cache: Dict[str, Tuple[float, Technicals]] = {}
_lock = threading.Lock()


def _ema(values: List[float], period: int) -> float:
    if not values:
        return 0.0
    k = 2.0 / (period + 1)
    ema = values[0]
    for v in values[1:]:
        ema = v * k + ema * (1 - k)
    return ema


def _wilder_rsi(closes: List[float], period: int = 14) -> float:
    if len(closes) < period + 1:
        return 0.0
    gains, losses = [], []
    for i in range(1, len(closes)):
        d = closes[i] - closes[i - 1]
        gains.append(max(d, 0.0))
        losses.append(max(-d, 0.0))
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
    if avg_loss == 0:
        return 100.0
    rs = avg_gain / avg_loss
    return round(100 - (100 / (1 + rs)), 2)


def _wilder_atr(highs, lows, closes, period: int = 14) -> float:
    n = len(closes)
    if n < period + 1:
        return 0.0
    trs = []
    for i in range(1, n):
        tr = max(
            highs[i] - lows[i],
            abs(highs[i] - closes[i - 1]),
            abs(lows[i] - closes[i - 1]),
        )
        trs.append(tr)
    atr = sum(trs[:period]) / period
    for i in range(period, len(trs)):
        atr = (atr * (period - 1) + trs[i]) / period
    return round(atr, 2)


def _session_vwap(candles: List[dict]) -> float:
    """Cumulative typical-price VWAP anchored to the most recent session day."""
    if not candles:
        return 0.0
    last_day = datetime.fromtimestamp(candles[-1]["time"]).date()
    cum_pv = cum_v = 0.0
    for c in candles:
        if datetime.fromtimestamp(c["time"]).date() != last_day:
            continue
        typical = (c["high"] + c["low"] + c["close"]) / 3.0
        cum_pv += typical * c["volume"]
        cum_v += c["volume"]
    if cum_v <= 0:
        # fall back to whole-window VWAP if the last day had no volume
        cum_pv = sum(((c["high"] + c["low"] + c["close"]) / 3.0) * c["volume"] for c in candles)
        cum_v = sum(c["volume"] for c in candles)
    return round(cum_pv / cum_v, 2) if cum_v else 0.0


def _supertrend(highs, lows, closes, period: int = 10, mult: float = 3.0) -> str:
    """Stateful Supertrend with final-band ratchet + flip logic. Returns
    the current direction: UP (bullish) / DOWN (bearish)."""
    n = len(closes)
    if n < period + 1:
        return "NEUTRAL"
    # ATR series (Wilder) computed incrementally alongside the bands
    atr = _wilder_atr(highs, lows, closes, period)
    if atr <= 0:
        return "NEUTRAL"
    final_upper = final_lower = None
    direction = "UP"
    for i in range(1, n):
        hl2 = (highs[i] + lows[i]) / 2.0
        basic_upper = hl2 + mult * atr
        basic_lower = hl2 - mult * atr
        if final_upper is None:
            final_upper, final_lower = basic_upper, basic_lower
            continue
        # ratchet: bands only tighten unless price breaks them
        final_upper = basic_upper if (basic_upper < final_upper or closes[i - 1] > final_upper) else final_upper
        final_lower = basic_lower if (basic_lower > final_lower or closes[i - 1] < final_lower) else final_lower
        if closes[i] > final_upper:
            direction = "UP"
        elif closes[i] < final_lower:
            direction = "DOWN"
    return direction


def _fetch_candles(symbol: str, exchange: str, broker) -> List[dict]:
    """Fetch real OHLCV candles from Angel One getCandleData. Returns a list
    of {time, open, high, low, close, volume} or [] on any failure."""
    if broker is None or not hasattr(broker, "historical_data") or not hasattr(broker, "_get_symbol_token"):
        return []
    try:
        actual_symbol, token = broker._get_symbol_token(symbol, exchange)
        if not token or token == "0":
            return []
        to_dt = datetime.now()
        from_dt = to_dt - timedelta(minutes=_LOOKBACK_MIN)
        params = {
            "exchange":    exchange,
            "symboltoken": str(token),
            "interval":    _INTERVAL,
            "fromdate":    from_dt.strftime("%Y-%m-%d %H:%M"),
            "todate":      to_dt.strftime("%Y-%m-%d %H:%M"),
        }
        resp = broker.historical_data(params)
        rows = (resp.get("data") if isinstance(resp, dict) else None) or []
        candles = []
        for r in rows:
            # Angel One returns [timestamp, open, high, low, close, volume]
            try:
                ts = r[0]
                epoch = datetime.fromisoformat(str(ts).replace("+05:30", "")).timestamp() if not isinstance(ts, (int, float)) else float(ts)
                candles.append({
                    "time": epoch, "open": float(r[1]), "high": float(r[2]),
                    "low": float(r[3]), "close": float(r[4]), "volume": int(r[5]),
                })
            except (IndexError, ValueError, TypeError):
                continue
        return candles
    except Exception as e:
        logger.debug(f"[Technicals] candle fetch failed for {symbol}: {e}")
        return []


def compute_technicals(symbol: str, exchange: str = "NSE", broker=None) -> Technicals:
    """Compute REAL technicals for `symbol`. Cached per symbol for _CACHE_TTL.
    Returns a Technicals with data_quality='real' only when real candles were
    fetched; otherwise 'unavailable' (callers must not trade on it)."""
    sym = symbol.upper().strip()
    now = time.time()
    with _lock:
        hit = _cache.get(sym)
        if hit and now - hit[0] < _CACHE_TTL:
            return hit[1]

    if broker is None:
        try:
            from app.api.broker_api import manager as _bm
            broker = _bm.get_broker("angelone") or _bm.broker
        except Exception:
            broker = None

    candles = _fetch_candles(sym, exchange, broker)
    if len(candles) < 30:
        t = Technicals(symbol=sym, data_quality="unavailable")
        with _lock:
            _cache[sym] = (now, t)
        return t

    closes = [c["close"] for c in candles]
    highs  = [c["high"] for c in candles]
    lows   = [c["low"] for c in candles]
    vols   = [c["volume"] for c in candles]
    ltp    = closes[-1]
    atr    = _wilder_atr(highs, lows, closes)
    avg_vol = sum(vols[-20:]) / min(len(vols), 20)

    t = Technicals(
        symbol=sym, data_quality="real", ltp=round(ltp, 2),
        rsi=_wilder_rsi(closes),
        ema_9=round(_ema(closes[-60:], 9), 2),
        ema_21=round(_ema(closes[-80:], 21), 2),
        vwap=_session_vwap(candles),
        atr=atr, atr_pct=round(atr / ltp * 100, 2) if ltp else 0.0,
        supertrend=_supertrend(highs, lows, closes),
        volume_ratio=round(vols[-1] / avg_vol, 2) if avg_vol else 0.0,
        candles_used=len(candles),
    )
    with _lock:
        _cache[sym] = (now, t)
    return t
