"""
core/analysis/ict.py — ICT / SMC Konzepte
BOS, FVG, Asia Levels, Liquidity Sweep, Order Block, Ichimoku
"""

from __future__ import annotations


def calc_bos(highs: list, lows: list, closes: list,
             lookback: int = 30, pivot_win: int = 3) -> dict:
    """
    Break of Structure (ICT/SMC).
    Rückgabe: {'bos': 'bullish'|'bearish'|None, 'bos_level': float|None, 'bars_ago': int|None}
    """
    n = len(closes)
    if n < lookback + pivot_win + 3:
        return {"bos": None, "bos_level": None, "bars_ago": None}

    w           = pivot_win
    search_end  = n - 1
    last_swing_high = last_swing_low = None

    for i in range(search_end - w - 1, max(w, search_end - lookback - 1), -1):
        lo = max(0, i - w); hi_r = min(n - 1, i + w)
        if last_swing_high is None and highs[i] == max(highs[lo : hi_r + 1]):
            last_swing_high = highs[i]
        if last_swing_low is None and lows[i] == min(lows[lo : hi_r + 1]):
            last_swing_low = lows[i]
        if last_swing_high is not None and last_swing_low is not None:
            break

    if last_swing_high is None or last_swing_low is None:
        return {"bos": None, "bos_level": None, "bars_ago": None}

    for ago in range(1, 6):
        if n - ago - 1 < 1:
            break
        c_now  = closes[n - ago]
        c_prev = closes[n - ago - 1]
        if c_now < last_swing_low <= c_prev:
            return {"bos": "bearish", "bos_level": last_swing_low,  "bars_ago": ago}
        if c_now > last_swing_high >= c_prev:
            return {"bos": "bullish", "bos_level": last_swing_high, "bars_ago": ago}

    return {"bos": None, "bos_level": None, "bars_ago": None}


def calc_fvg(highs: list, lows: list, closes: list, lookback: int = 20) -> dict:
    """
    Fair Value Gap / Imbalance (ICT-Definition).
    Rückgabe: {'type': 'bullish'|'bearish'|None, 'top', 'bottom', 'mid', 'filled_pct', 'bars_ago'}
    """
    n = len(closes)
    if n < 4:
        return {"type": None}
    cur = closes[-1]

    for i in range(n - 3, max(1, n - lookback - 2), -1):
        if i + 2 >= n:
            continue
        h_before = highs[i - 1]; l_before = lows[i - 1]
        h_after  = highs[i + 1]; l_after  = lows[i + 1]

        if h_before < l_after:
            bottom, top = h_before, l_after
            if top <= bottom:
                continue
            filled_pct = max(0.0, min(100.0, (cur - bottom) / (top - bottom) * 100))
            if filled_pct < 100.0:
                return {"type": "bullish", "top": top, "bottom": bottom,
                        "mid": (top + bottom) / 2,
                        "filled_pct": round(filled_pct, 0), "bars_ago": n - 2 - i}

        elif l_before > h_after:
            bottom, top = h_after, l_before
            if top <= bottom:
                continue
            filled_pct = max(0.0, min(100.0, (top - cur) / (top - bottom) * 100))
            if filled_pct < 100.0:
                return {"type": "bearish", "top": top, "bottom": bottom,
                        "mid": (top + bottom) / 2,
                        "filled_pct": round(filled_pct, 0), "bars_ago": n - 2 - i}

    return {"type": None}


def calc_asia_levels(bars: list) -> dict | None:
    """
    Asien-Session Hoch/Tief (00:00–08:00 UTC) aus M15-Bars.
    Rückgabe: {'high': float, 'low': float, 'n': int} oder None.
    """
    if not bars:
        return None
    import time as _time
    from datetime import datetime, timezone as _tz
    now_ts    = _time.time()
    today_utc = datetime.fromtimestamp(now_ts, tz=_tz.utc).replace(
        hour=0, minute=0, second=0, microsecond=0)
    today_ts    = today_utc.timestamp()
    asia_end_ts = today_ts + 8 * 3600

    asia_bars = [b for b in bars
                 if today_ts <= int(b["time"]) < asia_end_ts]
    if not asia_bars:
        return None
    return {
        "high": max(float(b["high"]) for b in asia_bars),
        "low":  min(float(b["low"])  for b in asia_bars),
        "n":    len(asia_bars),
    }


def calc_liquidity_sweep(highs: list, lows: list, closes: list, opens: list,
                         lookback: int = 25, pivot_win: int = 3) -> dict:
    """
    Liquidity Sweep (ICT): Wick über Swing-High/-Low, Schluss zurück.
    Rückgabe: {'sweep': 'bearish'|'bullish'|None, 'level': float|None, 'bars_ago': int|None}
    """
    n = len(closes)
    if n < lookback + pivot_win + 3:
        return {"sweep": None, "level": None, "bars_ago": None}

    w = pivot_win
    search_end = n - 1
    swing_highs = []
    swing_lows  = []

    for i in range(max(w, search_end - lookback), search_end - w):
        lo = max(0, i - w); hi_r = min(n - 1, i + w)
        if highs[i] == max(highs[lo : hi_r + 1]):
            swing_highs.append(highs[i])
        if lows[i] == min(lows[lo : hi_r + 1]):
            swing_lows.append(lows[i])

    if not swing_highs or not swing_lows:
        return {"sweep": None, "level": None, "bars_ago": None}

    pivot_high = max(swing_highs)
    pivot_low  = min(swing_lows)

    for ago in range(1, 4):
        idx = n - ago - 1
        if idx < 1:
            break
        h = highs[idx]; l = lows[idx]; c = closes[idx]
        if h > pivot_high and c < pivot_high:
            return {"sweep": "bearish", "level": pivot_high, "bars_ago": ago}
        if l < pivot_low  and c > pivot_low:
            return {"sweep": "bullish", "level": pivot_low,  "bars_ago": ago}

    return {"sweep": None, "level": None, "bars_ago": None}


def calc_order_block(highs: list, lows: list, closes: list, opens: list,
                     lookback: int = 40, min_impulse_bars: int = 3,
                     atr: float | None = None) -> dict:
    """
    Order Block (ICT/SMC).
    Bullish OB: letzter Bear-Candle vor starkem Aufwärts-Impuls → Support-Zone
    Bearish OB: letzter Bull-Candle vor starkem Abwärts-Impuls  → Resistance-Zone
    Rückgabe: {'type': 'bullish'|'bearish'|None, 'high', 'low', 'mid', 'bars_ago', 'mitigated'}
    """
    n = len(closes)
    if n < lookback + min_impulse_bars + 2:
        return {"type": None}

    atr_eff  = atr if atr and atr > 0 else 0.5
    min_move = 1.5 * atr_eff

    for end in range(n - min_impulse_bars - 1, max(1, n - lookback - 1), -1):
        if end + min_impulse_bars >= n:
            continue

        bull_move = closes[end + min_impulse_bars] - closes[end]
        bear_move = closes[end] - closes[end + min_impulse_bars]

        if bull_move > min_move:
            for ob_i in range(end, max(0, end - 6), -1):
                if closes[ob_i] < opens[ob_i]:
                    ob_h = highs[ob_i]; ob_l = lows[ob_i]
                    mit  = any(lows[j] < ob_h and highs[j] > ob_l
                               for j in range(ob_i + 1, n))
                    return {"type": "bullish", "high": ob_h, "low": ob_l,
                            "mid": (ob_h + ob_l) / 2,
                            "bars_ago": n - 1 - ob_i, "mitigated": mit}

        elif bear_move > min_move:
            for ob_i in range(end, max(0, end - 6), -1):
                if closes[ob_i] > opens[ob_i]:
                    ob_h = highs[ob_i]; ob_l = lows[ob_i]
                    mit  = any(highs[j] > ob_l and lows[j] < ob_h
                               for j in range(ob_i + 1, n))
                    return {"type": "bearish", "high": ob_h, "low": ob_l,
                            "mid": (ob_h + ob_l) / 2,
                            "bars_ago": n - 1 - ob_i, "mitigated": mit}

    return {"type": None}


def calc_coc(highs: list, lows: list, closes: list,
             lookback: int = 50, pivot_win: int = 3) -> dict:
    """
    Change of Character (CoC / CHOCH) — ICT/SMC Trendumkehrsignal.

    Algorithmus:
      1. Finde das jüngste Swing-High UND das jüngste Swing-Low im Lookback.
      2. Welches Extrem ist jünger bestimmt den vorherigen Bias:
         • SH jünger → Uptrend → suche das letzte Swing-Low VOR dem SH (= Higher Low)
           Wenn Close unter dieses HL bricht → bearischer CoC
         • SL jünger → Downtrend → suche das letzte Swing-High VOR dem SL (= Lower High)
           Wenn Close über dieses LH bricht → bullischer CoC

    Unterschied zu BOS:
      BOS = Strukturbruch IN Trendrichtung (Fortsetzung)
      CoC = Strukturbruch GEGEN den Trend  (Umkehrsignal, stärker)

    Rückgabe:
      coc:            'bearish' | 'bullish' | None
      coc_level:      gebrochenes Strukturniveau (Higher Low / Lower High)
      swing_extreme:  letztes Swing-Extrem (SH/SL = der Pivot der den Trend definierte)
      bars_ago:       Bars seit dem Bruch
    """
    n = len(closes)
    if n < pivot_win * 2 + 12:
        return {"coc": None, "coc_level": None, "swing_extreme": None, "bars_ago": None}

    w       = pivot_win
    lb      = min(lookback, n - w - 2)

    def _find_pivot(seq_high: bool, start: int, stop: int) -> tuple[int, float] | None:
        for i in range(start, max(w, stop), -1):
            lo = max(0, i - w); hi_r = min(n - 1, i + w)
            if seq_high and highs[i] == max(highs[lo:hi_r + 1]):
                return (i, highs[i])
            if not seq_high and lows[i] == min(lows[lo:hi_r + 1]):
                return (i, lows[i])
        return None

    # ── Jüngstes Swing-High und Swing-Low im Lookback ────────────────────────
    recent_sh = _find_pivot(True,  n - 1 - w, n - lb - 1)
    recent_sl = _find_pivot(False, n - 1 - w, n - lb - 1)

    if recent_sh is None or recent_sl is None:
        return {"coc": None, "coc_level": None, "swing_extreme": None, "bars_ago": None}

    sh_idx, sh_price = recent_sh
    sl_idx, sl_price = recent_sl

    lb_stop = max(w, n - lb - 1)   # ältestes Bar das in Lookback fällt

    # ── Bearish CoC: letztes Extrem war ein Swing-High ────────────────────────
    if sh_idx > sl_idx:
        # Suche den Swing-Low VOR dem SH (= der Higher Low im Uptrend)
        # Suchbereich: komplett rückwärts bis Ende des Lookback-Fensters
        hl = _find_pivot(False, sh_idx - w - 1, lb_stop)
        if hl is None:
            return {"coc": None, "coc_level": None, "swing_extreme": None, "bars_ago": None}
        hl_price = hl[1]
        for ago in range(1, 10):
            if n - ago - 1 < 1:
                break
            c_now  = closes[n - ago]
            c_prev = closes[n - ago - 1]
            if c_now < hl_price <= c_prev:
                return {"coc": "bearish", "coc_level": round(hl_price, 5),
                        "swing_extreme": round(sh_price, 5), "bars_ago": ago}

    # ── Bullish CoC: letztes Extrem war ein Swing-Low ─────────────────────────
    elif sl_idx > sh_idx:
        # Suche den Swing-High VOR dem SL (= der Lower High im Downtrend)
        lh = _find_pivot(True, sl_idx - w - 1, lb_stop)
        if lh is None:
            return {"coc": None, "coc_level": None, "swing_extreme": None, "bars_ago": None}
        lh_price = lh[1]
        for ago in range(1, 10):
            if n - ago - 1 < 1:
                break
            c_now  = closes[n - ago]
            c_prev = closes[n - ago - 1]
            if c_now > lh_price >= c_prev:
                return {"coc": "bullish", "coc_level": round(lh_price, 5),
                        "swing_extreme": round(sl_price, 5), "bars_ago": ago}

    return {"coc": None, "coc_level": None, "swing_extreme": None, "bars_ago": None}


def calc_ichimoku(highs: list, lows: list, closes: list,
                  tenkan: int = 9, kijun: int = 26, senkou_b: int = 52) -> dict | None:
    """
    Ichimoku Kinko Hyo — Wolken-Analyse (Standard 9/26/52).
    ichi_bias: 4=strong_bull, 3=bull, 2=neutral, 1=bear, 0=strong_bear
    """
    n = len(closes)
    if n < senkou_b + kijun + 1:
        return None

    def midpoint(h_sl, l_sl):
        return (max(h_sl) + min(l_sl)) / 2

    tenkan_val = midpoint(highs[-tenkan:],  lows[-tenkan:])
    kijun_val  = midpoint(highs[-kijun:],   lows[-kijun:])

    off = kijun
    if n - off - 1 < senkou_b:
        return None
    idx = n - off - 1

    t_ago = midpoint(highs[idx - tenkan  + 1: idx + 1], lows[idx - tenkan  + 1: idx + 1])
    k_ago = midpoint(highs[idx - kijun   + 1: idx + 1], lows[idx - kijun   + 1: idx + 1])
    a_val = (t_ago + k_ago) / 2
    b_val = midpoint(highs[idx - senkou_b + 1: idx + 1], lows[idx - senkou_b + 1: idx + 1])

    cloud_top = max(a_val, b_val)
    cloud_bot = min(a_val, b_val)
    cur       = closes[-1]

    price_vs_cloud = ("above" if cur > cloud_top else
                      "below" if cur < cloud_bot else "inside")
    tk_signal      = "bullish" if tenkan_val >= kijun_val else "bearish"
    cloud_color    = "green"   if a_val >= b_val else "red"

    chikou_signal = "neutral"
    if n > kijun:
        ref = closes[n - 1 - kijun]
        chikou_signal = "bullish" if cur > ref else ("bearish" if cur < ref else "neutral")

    bull_pts = (
        (1 if price_vs_cloud == "above" else 0) +
        (1 if tk_signal       == "bullish" else 0) +
        (1 if chikou_signal   == "bullish" else 0) +
        (1 if cloud_color     == "green"   else 0)
    )
    ichi_bias = {4: "strong_bull", 3: "bull", 1: "bear", 0: "strong_bear"}.get(bull_pts, "neutral")

    return {
        "tenkan":         round(tenkan_val, 3),
        "kijun":          round(kijun_val,  3),
        "senkou_a":       round(a_val, 3),
        "senkou_b":       round(b_val, 3),
        "cloud_top":      round(cloud_top, 3),
        "cloud_bot":      round(cloud_bot, 3),
        "cloud_color":    cloud_color,
        "price_vs_cloud": price_vs_cloud,
        "tk_signal":      tk_signal,
        "chikou_signal":  chikou_signal,
        "ichi_bias":      ichi_bias,
        "bull_pts":       bull_pts,
    }
