from __future__ import annotations

from typing import Any

from crypto_spot_bot.models import Candle, Ticker, utc_now


def market_quality_snapshot(
    *,
    symbols: list[str],
    candles_by_symbol: dict[str, list[Candle]],
    tickers: dict[str, Ticker],
    interval: str,
) -> dict[str, Any]:
    rows = [
        analyze_symbol_quality(
            symbol=symbol,
            candles=candles_by_symbol.get(symbol, []),
            ticker=tickers.get(symbol),
            interval=interval,
        )
        for symbol in symbols
    ]
    worst_score = min((row["score"] for row in rows), default=0.0)
    status = "ok"
    if any(row["status"] == "error" for row in rows):
        status = "error"
    elif any(row["status"] == "warn" for row in rows):
        status = "warn"
    return {
        "status": status,
        "score": round(worst_score, 4),
        "symbols": rows,
    }


def analyze_symbol_quality(
    *,
    symbol: str,
    candles: list[Candle],
    ticker: Ticker | None,
    interval: str,
) -> dict[str, Any]:
    issues: list[dict[str, Any]] = []
    score = 1.0
    interval_ms = _interval_ms(interval)
    if not candles:
        return _row(symbol, "error", 0.0, [{"code": "no_candles", "severity": "error"}], 0, None, None)

    timestamps = [candle.timestamp for candle in candles]
    duplicates = len(timestamps) - len(set(timestamps))
    if duplicates:
        issues.append({"code": "duplicate_candles", "severity": "warn", "count": duplicates})
        score -= min(0.25, duplicates * 0.03)

    invalid_ohlc = 0
    zero_volume = 0
    for candle in candles:
        prices = [candle.open, candle.high, candle.low, candle.close]
        if any(price <= 0 for price in prices) or candle.high < max(candle.open, candle.close) or candle.low > min(candle.open, candle.close):
            invalid_ohlc += 1
        if candle.volume <= 0:
            zero_volume += 1
    if invalid_ohlc:
        issues.append({"code": "invalid_ohlc", "severity": "error", "count": invalid_ohlc})
        score -= min(0.45, invalid_ohlc * 0.08)
    if zero_volume:
        issues.append({"code": "zero_volume", "severity": "warn", "count": zero_volume})
        score -= min(0.20, zero_volume * 0.02)

    missing_gaps = 0
    largest_gap_ms = 0
    if interval_ms > 0:
        ordered = sorted(set(timestamps))
        for left, right in zip(ordered, ordered[1:]):
            gap = right - left
            largest_gap_ms = max(largest_gap_ms, gap)
            if gap > interval_ms * 1.5:
                missing_gaps += max(1, round(gap / interval_ms) - 1)
        if missing_gaps:
            issues.append({"code": "missing_candles", "severity": "warn", "count": missing_gaps})
            score -= min(0.35, missing_gaps * 0.04)

        age_seconds = max(0.0, (utc_now().timestamp() * 1000 - max(timestamps)) / 1000)
        stale_after = interval_ms / 1000 * 2.5
        if age_seconds > stale_after:
            issues.append({"code": "stale_candles", "severity": "warn", "age_seconds": round(age_seconds, 1)})
            score -= 0.20
    else:
        age_seconds = None

    if ticker is None:
        issues.append({"code": "no_ticker", "severity": "error"})
        score -= 0.45
    else:
        if ticker.last_price <= 0:
            issues.append({"code": "invalid_ticker_price", "severity": "error"})
            score -= 0.35
        if ticker.spread_percent > 0.35:
            issues.append({"code": "wide_spread", "severity": "warn", "spread_percent": round(ticker.spread_percent, 4)})
            score -= 0.15

    score = max(0.0, min(1.0, score))
    severity = {str(issue.get("severity")) for issue in issues}
    status = "error" if "error" in severity else "warn" if "warn" in severity else "ok"
    return _row(
        symbol,
        status,
        score,
        issues,
        len(candles),
        max(timestamps),
        largest_gap_ms if interval_ms > 0 else None,
    )


def _row(
    symbol: str,
    status: str,
    score: float,
    issues: list[dict[str, Any]],
    candle_count: int,
    last_candle_timestamp: int | None,
    largest_gap_ms: int | None,
) -> dict[str, Any]:
    return {
        "symbol": symbol,
        "status": status,
        "score": round(score, 4),
        "candle_count": candle_count,
        "last_candle_timestamp": last_candle_timestamp,
        "largest_gap_ms": largest_gap_ms,
        "issues": issues,
    }


def _interval_ms(interval: str) -> int:
    normalized = str(interval).strip().upper()
    if normalized == "D":
        return 24 * 60 * 60 * 1000
    if normalized == "W":
        return 7 * 24 * 60 * 60 * 1000
    if normalized.isdigit():
        return int(normalized) * 60 * 1000
    return 0
