"""
scorer.py

Transparent scoring engine. Every score comes with a `breakdown` dict
showing exactly how many points each factor contributed, so a stock's
rank is always explainable — never a black box, per project requirement.

Entry point:
    score_premarket_candidate(symbol, bars, pm_high, pm_low, avg_vol_baseline)
        -> used by premarket_scanner.py for the single full-universe scan
           at schedule.premarket_scan_time (see monitor.py's module
           docstring -- the 09:00-09:25 development-monitoring window and
           09:25 final-scoring review that used to call this repeatedly
           per symbol were eliminated 2026-09-01)

All weights and thresholds come from config.json under
"premarket_scoring" — nothing here is hard-coded strategy tuning.
"""

from config_loader import get_config
from indicators import (
    vwap, vwap_slope, relative_volume, volume_acceleration, price_momentum,
    is_higher_highs_higher_lows, consolidation_tightness, distance_from_high_pct,
    extension_from_vwap_pct, spread_pct,
)


def _clamp(x, lo=0.0, hi=100.0):
    return max(lo, min(hi, x))


def score_premarket_candidate(symbol: str, bars: list, pm_high: float, pm_low: float,
                               avg_vol_baseline: float, bid: float = None, ask: float = None) -> dict:
    """
    bars: list of 1-min premarket bars, oldest first, each
          {"t","o","h","l","c","v"}
    pm_high / pm_low: premarket high/low so far
    avg_vol_baseline: 20-day average volume (or similar) used for RVOL
    bid/ask: latest quote, optional (affects spread/liquidity subscore)

    Returns: {"symbol", "total_score", "breakdown": {...}, "flags": {...}}
    """
    cfg = get_config()["premarket_scoring"]
    w = cfg["weights"]

    if not bars:
        return {"symbol": symbol, "total_score": 0.0, "breakdown": {}, "flags": {"no_data": True}}

    closes = [b["c"] for b in bars]
    volumes = [b["v"] for b in bars]
    current_price = closes[-1]
    total_volume = sum(volumes)

    v_wap = vwap(bars)
    v_slope = vwap_slope(bars, lookback=min(cfg["vwap_lookback_bars"], max(1, len(bars) - 1)))
    rvol = relative_volume(total_volume, avg_vol_baseline)
    vol_accel = volume_acceleration(volumes)
    momentum = price_momentum(closes[-cfg["momentum_lookback_snapshots"]:] if len(closes) >= cfg["momentum_lookback_snapshots"] else closes)
    hh_hl = is_higher_highs_higher_lows(bars, lookback=min(4, len(bars)))
    tightness = consolidation_tightness(bars)
    dist_from_high = distance_from_high_pct(current_price, pm_high)
    extension = extension_from_vwap_pct(current_price, v_wap)
    spr = spread_pct(bid, ask) if bid and ask else 0.5  # neutral assumption if no quote

    breakdown = {}

    # --- Price quality: is price actually inside the $5-$15 tradable band with room? ---
    price_min = get_config()["universe"]["price_min"]
    price_max = get_config()["universe"]["price_max"]
    if price_min <= current_price <= price_max:
        # Favor the middle of the band slightly (avoids edge-of-range illiquid names)
        mid = (price_min + price_max) / 2
        band_half = (price_max - price_min) / 2
        closeness = 1 - abs(current_price - mid) / band_half
        breakdown["price_quality"] = w["price_quality"] * max(0.3, closeness)
    else:
        breakdown["price_quality"] = 0.0

    # --- Volume quality: absolute premarket volume vs minimum threshold ---
    min_vol = cfg["min_premarket_volume"]
    if total_volume >= min_vol:
        vol_ratio = min(total_volume / (min_vol * 5), 1.0)  # saturate at 5x minimum
        breakdown["volume_quality"] = w["volume_quality"] * vol_ratio
    else:
        breakdown["volume_quality"] = w["volume_quality"] * (total_volume / min_vol) * 0.3

    # --- RVOL ---
    min_rvol = cfg["min_rvol"]
    if rvol >= min_rvol:
        rvol_ratio = min(rvol / (min_rvol * 3), 1.0)
        breakdown["rvol"] = w["rvol"] * rvol_ratio
    else:
        breakdown["rvol"] = w["rvol"] * (rvol / min_rvol) * 0.3 if min_rvol > 0 else 0.0

    # --- Volume acceleration: rewards developing volume, not one spike ---
    accel_min = cfg["acceleration_min_ratio"]
    if vol_accel >= accel_min:
        accel_ratio = min((vol_accel - 1.0) / 1.0, 1.0)
        breakdown["volume_acceleration"] = w["volume_acceleration"] * _clamp(accel_ratio, 0, 1)
    else:
        breakdown["volume_acceleration"] = w["volume_acceleration"] * _clamp(vol_accel / accel_min, 0, 1) * 0.4

    # --- Price momentum ---
    breakdown["price_momentum"] = w["price_momentum"] * _clamp(momentum / 8.0, 0, 1) if momentum > 0 else 0.0

    # --- Price structure (higher highs / higher lows) ---
    breakdown["price_structure"] = w["price_structure"] if hh_hl else w["price_structure"] * 0.2

    # --- VWAP structure: price above VWAP is healthy ---
    breakdown["vwap_structure"] = w["vwap_structure"] if current_price > v_wap else w["vwap_structure"] * 0.15

    # --- VWAP slope: rising VWAP is healthy ---
    breakdown["vwap_slope"] = w["vwap_slope"] if v_slope > 0 else w["vwap_slope"] * 0.1

    # --- Consolidation quality: tight range = healthier base ---
    # Lower tightness % is better, scale so <=3% is full credit, >=15% is zero.
    consolidation_score = _clamp((15 - tightness) / (15 - 3), 0, 1)
    breakdown["consolidation_quality"] = w["consolidation_quality"] * consolidation_score

    # --- Distance from PM high: too far = weak, essentially at high = strong ---
    max_dist = cfg["max_distance_from_pm_high_pct"]
    dist_score = _clamp((max_dist - dist_from_high) / max_dist, 0, 1)
    breakdown["distance_from_pm_high"] = w["distance_from_pm_high"] * dist_score

    # --- Spread / liquidity ---
    max_spread = cfg["max_spread_pct"]
    spread_score = _clamp((max_spread - spr) / max_spread, 0, 1)
    breakdown["spread_liquidity"] = w["spread_liquidity"] * spread_score

    # --- Momentum consistency: penalize choppy back-and-forth closes ---
    if len(closes) >= 3:
        diffs = [closes[i] - closes[i - 1] for i in range(1, len(closes))]
        same_direction = sum(1 for d in diffs if d > 0)
        consistency = same_direction / len(diffs)
        breakdown["momentum_consistency"] = w["momentum_consistency"] * consistency
    else:
        breakdown["momentum_consistency"] = w["momentum_consistency"] * 0.5

    # --- Extension risk: penalty for already-extended-from-VWAP names ---
    ext_threshold = cfg["extension_risk_pct_threshold"]
    if extension > ext_threshold:
        over = min((extension - ext_threshold) / ext_threshold, 1.0)
        breakdown["extension_risk_penalty"] = -w["extension_risk_penalty"] * over
    else:
        breakdown["extension_risk_penalty"] = 0.0

    total = sum(breakdown.values())
    max_possible = sum(v for k, v in w.items() if k != "extension_risk_penalty")
    normalized = _clamp(total / max_possible * 100.0, 0, 100)

    flags = {
        "meets_min_volume": total_volume >= min_vol,
        "meets_min_rvol": rvol >= min_rvol,
        "spread_ok": spr <= max_spread,
        "extended": extension > ext_threshold,
        "hh_hl_structure": hh_hl,
        "price_above_vwap": current_price > v_wap,
        "vwap_rising": v_slope > 0,
    }

    return {
        "symbol": symbol,
        "total_score": round(normalized, 2),
        "raw_score": round(total, 2),
        "breakdown": {k: round(v, 2) for k, v in breakdown.items()},
        "metrics": {
            "price": current_price,
            "vwap": round(v_wap, 4),
            "vwap_slope": round(v_slope, 4),
            "rvol": round(rvol, 2),
            "volume_acceleration": round(vol_accel, 2),
            "premarket_volume": total_volume,
            "momentum_pct": round(momentum, 2),
            "consolidation_tightness_pct": round(tightness, 2),
            "distance_from_pm_high_pct": round(dist_from_high, 2),
            "extension_from_vwap_pct": round(extension, 2),
            "spread_pct": round(spr, 2),
        },
        "flags": flags,
    }
