from __future__ import annotations

import math
from statistics import fmean
from typing import Any


def number(value: Any) -> float | None:
    try:
        result = float(value)
        return result if math.isfinite(result) else None
    except (TypeError, ValueError):
        return None


def change(current: float | None, previous: float | None) -> float | None:
    if current is None or previous in (None, 0):
        return None
    return (current / previous - 1) * 100


def mean(values: list[float | None]) -> float | None:
    valid = [value for value in values if value is not None]
    return fmean(valid) if valid else None


def ratio(numerator: float | None, denominator: float | None) -> float | None:
    if numerator is None or denominator in (None, 0):
        return None
    return numerator / denominator


def rsi(closes: list[float], period: int = 6) -> float | None:
    if len(closes) <= period:
        return None
    differences = [closes[index] - closes[index - 1] for index in range(1, len(closes))]
    recent = differences[-period:]
    gains = sum(max(value, 0) for value in recent) / period
    losses = sum(max(-value, 0) for value in recent) / period
    if losses == 0:
        return 100.0 if gains > 0 else 50.0
    return 100 - 100 / (1 + gains / losses)


def ema(values: list[float], period: int) -> list[float]:
    if not values:
        return []
    alpha = 2 / (period + 1)
    result = [values[0]]
    for value in values[1:]:
        result.append(value * alpha + result[-1] * (1 - alpha))
    return result


def macd(values: list[float]) -> tuple[list[float], list[float]]:
    fast = ema(values, 12)
    slow = ema(values, 26)
    difference = [left - right for left, right in zip(fast, slow, strict=True)]
    return difference, ema(difference, 9)


def weekly_series(rows: list[dict[str, Any]]) -> tuple[list[float], list[float]]:
    weeks: dict[str, dict[str, float]] = {}
    for row in rows:
        date = str(row.get("trade_date") or "")
        if len(date) != 10:
            continue
        from datetime import date as date_type

        parsed = date_type.fromisoformat(date)
        key = f"{parsed.isocalendar().year}-{parsed.isocalendar().week:02d}"
        weeks.setdefault(key, {"close": 0.0, "amount": 0.0})
        close = number(row.get("close"))
        amount = number(row.get("amount"))
        if close is not None:
            weeks[key]["close"] = close
        if amount is not None:
            weeks[key]["amount"] += amount
    values = list(weeks.values())
    return [item["close"] for item in values], [item["amount"] for item in values]


def percentile_map(rows: list[dict[str, Any]], field: str, direction: str) -> dict[str, float]:
    valid = [row for row in rows if number(row.get(field)) is not None]
    ordered = sorted(
        valid,
        key=lambda row: (
            number(row[field]) if direction == "asc" else -float(number(row[field]) or 0),
            str(row["identifier"]),
        ),
    )
    if len(ordered) == 1:
        return {str(ordered[0]["identifier"]): 1.0}
    return {
        str(row["identifier"]): 1 - index / (len(ordered) - 1) for index, row in enumerate(ordered)
    }


def pearson(left: list[float], right: list[float]) -> float:
    if len(left) < 3 or len(left) != len(right):
        return 0.0
    left_mean = fmean(left)
    right_mean = fmean(right)
    numerator = sum(
        (first - left_mean) * (second - right_mean)
        for first, second in zip(left, right, strict=True)
    )
    left_scale = math.sqrt(sum((value - left_mean) ** 2 for value in left))
    right_scale = math.sqrt(sum((value - right_mean) ** 2 for value in right))
    return numerator / (left_scale * right_scale) if left_scale and right_scale else 0.0


def rounded(value: float | None, digits: int = 4) -> float | None:
    return round(value, digits) if value is not None else None


def calculate_earnings_quality(bars: list[dict[str, Any]], announcement_date: str) -> bool | None:
    index = next(
        (
            offset
            for offset, row in enumerate(bars)
            if str(row.get("trade_date") or "") == announcement_date
        ),
        -1,
    )
    if index < 0:
        return None
    prior = [
        number(row.get("vol"))
        for row in bars[max(0, index - 5) : index]
        if number(row.get("vol")) is not None
    ]
    baseline = mean(prior)
    current = bars[index]
    volume_ratio = ratio(number(current.get("vol")), baseline)
    bad = (
        number(current.get("close")) is not None
        and number(current.get("open")) is not None
        and float(number(current["close"]) or 0) < float(number(current["open"]) or 0)
        and float(number(current.get("pct_chg")) or 0) < 0
        and volume_ratio is not None
        and volume_ratio >= 1.8
    )
    return not bad
