"""Benchmark: download() with many symbols — dyfinance vs yfinance.

Usage:
    python benchmarks/bench_multi_symbol.py
    python benchmarks/bench_multi_symbol.py -n 10 --sizes 5 10 20
"""

from __future__ import annotations

import argparse
import statistics
import time

import yfinance
import dyfinance

# S&P 500 top constituents by weight (well-known, liquid tickers)
SYMBOL_POOL = [
    "AAPL", "MSFT", "GOOGL", "AMZN", "NVDA",
    "META", "TSLA", "BRK-B", "LLY", "AVGO",
    "JPM", "UNH", "V", "XOM", "MA",
    "PG", "COST", "JNJ", "HD", "ABBV",
    "WMT", "NFLX", "BAC", "KO", "CRM",
    "MRK", "CVX", "ORCL", "AMD", "PEP",
    "TMO", "CSCO", "ACN", "LIN", "ADBE",
    "MCD", "ABT", "WFC", "DHR", "TXN",
    "PM", "NEE", "DIS", "QCOM", "AMGN",
    "ISRG", "INTU", "GE", "CAT", "IBM",
]


def _bench(fn, n: int) -> list[float]:
    times = []
    for _ in range(n):
        t0 = time.perf_counter()
        fn()
        times.append(time.perf_counter() - t0)
    return times


def _stats(times: list[float]) -> dict[str, float]:
    return {
        "mean_ms": statistics.mean(times) * 1000,
        "median_ms": statistics.median(times) * 1000,
        "stdev_ms": statistics.stdev(times) * 1000 if len(times) > 1 else 0.0,
        "min_ms": min(times) * 1000,
        "max_ms": max(times) * 1000,
    }


def bench_download(tickers: list[str], period: str, n: int) -> dict:
    # warm-up: populate both caches
    print(f"    warming up {len(tickers)} tickers ...", end=" ", flush=True)
    yfinance.download(tickers, period=period, interval="1d")
    dyfinance.download(tickers, period=period, interval="1d")
    print("done")

    yf_times = _bench(
        lambda: yfinance.download(tickers, period=period, interval="1d"), n
    )
    dy_times = _bench(
        lambda: dyfinance.download(tickers, period=period, interval="1d"), n
    )

    return {"yfinance": _stats(yf_times), "dyfinance": _stats(dy_times)}


def _print_comparison(label: str, result: dict) -> None:
    yf = result["yfinance"]
    dy = result["dyfinance"]
    speedup = yf["median_ms"] / dy["median_ms"] if dy["median_ms"] else float("inf")

    print(f"\n{'=' * 64}")
    print(f"  {label}")
    print(f"{'=' * 64}")
    print(f"  {'':20s} {'yfinance (SQLite)':>20s} {'dyfinance (DuckDB)':>20s}")
    print(f"  {'median':20s} {yf['median_ms']:>17.1f} ms {dy['median_ms']:>17.1f} ms")
    print(f"  {'mean':20s} {yf['mean_ms']:>17.1f} ms {dy['mean_ms']:>17.1f} ms")
    print(f"  {'stdev':20s} {yf['stdev_ms']:>17.1f} ms {dy['stdev_ms']:>17.1f} ms")
    print(f"  {'min':20s} {yf['min_ms']:>17.1f} ms {dy['min_ms']:>17.1f} ms")
    print(f"  {'max':20s} {yf['max_ms']:>17.1f} ms {dy['max_ms']:>17.1f} ms")
    print(f"  {'─' * 62}")
    if speedup >= 1:
        print(f"  speedup (median): {speedup:.2f}x  <- dyfinance faster")
    else:
        print(f"  speedup (median): {1/speedup:.2f}x  <- yfinance faster")


def main() -> None:
    parser = argparse.ArgumentParser(description="Multi-symbol download benchmark")
    parser.add_argument("-n", type=int, default=10, help="iterations (default 10)")
    parser.add_argument(
        "--sizes", nargs="+", type=int, default=[5, 10, 20, 30, 50],
        help="number of tickers to test (default: 5 10 20 30 50)",
    )
    parser.add_argument(
        "--period", default="1mo", help="period to fetch (default: 1mo)",
    )
    args = parser.parse_args()

    print(f"Multi-symbol download() benchmark")
    print(f"Iterations: {args.n}, Period: {args.period}")
    print(f"Sizes: {args.sizes}")

    for size in args.sizes:
        tickers = SYMBOL_POOL[:size]
        result = bench_download(tickers, args.period, args.n)
        _print_comparison(f"download()  {size} tickers  period={args.period}", result)


if __name__ == "__main__":
    main()
