"""Market-side feature calculations from price bar data."""
from __future__ import annotations

from typing import Any

from libs.oracle_client.models import PriceBar


def reaction_day_return(bars: list[PriceBar], event_date: str) -> float | None:
    """(close - prev_close) / prev_close on event date."""
    dated = {b.date: b for b in bars}
    if event_date not in dated:
        return None
    event_bar = dated[event_date]
    # Find previous bar
    sorted_dates = sorted(dated.keys())
    idx = sorted_dates.index(event_date)
    if idx == 0:
        return None
    prev_bar = dated[sorted_dates[idx - 1]]
    if prev_bar.close == 0:
        return None
    return (event_bar.close - prev_bar.close) / prev_bar.close


def volume_ratio_20d(bars: list[PriceBar], event_date: str) -> float | None:
    """Event day volume / 20-day average volume before event."""
    dated = {b.date: b for b in bars}
    sorted_dates = sorted(dated.keys())
    if event_date not in dated:
        return None
    idx = sorted_dates.index(event_date)
    if idx < 1:
        return None
    prior = sorted_dates[max(0, idx - 20) : idx]
    if not prior:
        return None
    avg_vol = sum(dated[d].volume for d in prior) / len(prior)
    if avg_vol == 0:
        return None
    return dated[event_date].volume / avg_vol


def close_location(bar: PriceBar) -> float | None:
    """(close - low) / (high - low): 0=closed at low, 1=at high."""
    rng = bar.high - bar.low
    if rng == 0:
        return None
    return (bar.close - bar.low) / rng


def gap_size(bars: list[PriceBar], event_date: str) -> float | None:
    """(open_today - close_yesterday) / close_yesterday."""
    dated = {b.date: b for b in bars}
    sorted_dates = sorted(dated.keys())
    if event_date not in dated:
        return None
    idx = sorted_dates.index(event_date)
    if idx == 0:
        return None
    today = dated[event_date]
    yesterday = dated[sorted_dates[idx - 1]]
    if yesterday.close == 0:
        return None
    return (today.open - yesterday.close) / yesterday.close


def atr_14(bars: list[PriceBar]) -> float | None:
    """14-period Average True Range."""
    if len(bars) < 2:
        return None
    sorted_bars = sorted(bars, key=lambda b: b.date)
    true_ranges: list[float] = []
    for i in range(1, len(sorted_bars)):
        curr = sorted_bars[i]
        prev = sorted_bars[i - 1]
        tr = max(
            curr.high - curr.low,
            abs(curr.high - prev.close),
            abs(curr.low - prev.close),
        )
        true_ranges.append(tr)
    if len(true_ranges) < 14:
        return sum(true_ranges) / len(true_ranges) if true_ranges else None
    return sum(true_ranges[-14:]) / 14


def compute_market_features(
    bars: list[PriceBar], event_date: str
) -> dict[str, Any]:
    """Compute all market features for an event date."""
    dated = {b.date: b for b in bars}
    event_bar = dated.get(event_date)

    features: dict[str, Any] = {
        "reaction_day_return": reaction_day_return(bars, event_date),
        "volume_ratio_20d": volume_ratio_20d(bars, event_date),
        "gap_size": gap_size(bars, event_date),
        "atr_14": atr_14(bars),
    }
    if event_bar:
        features["close_location"] = close_location(event_bar)
        features["event_close"] = event_bar.close
        features["event_volume"] = event_bar.volume
    else:
        features["close_location"] = None
        features["event_close"] = None
        features["event_volume"] = None

    return features
