"""Benchmark: dyfinance (DuckDB) vs yfinance (SQLite) cached read performance.

Usage:
    python benchmarks/bench_cache.py            # default: 20 iterations
    python benchmarks/bench_cache.py -n 50      # 50 iterations
    python benchmarks/bench_cache.py --tickers AAPL MSFT GOOGL

Requires network access for the first warm-up fetch.
Subsequent iterations measure pure cache-read speed.
"""

from __future__ import annotations

import argparse
import statistics
import time

import pandas as pd
import yfinance
import dyfinance


# ── helpers ──────────────────────────────────────────────────────────────────

def _bench(fn, n: int) -> list[float]:
    """Run *fn* n times and return elapsed seconds for each call."""
    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,
    }


# ── single-ticker benchmark ─────────────────────────────────────────────────

def bench_ticker(ticker: str, period: str, n: int) -> dict:
    yf_ticker = yfinance.Ticker(ticker)
    dy_ticker = dyfinance.Ticker(ticker)

    # warm-up: populate both caches
    yf_ticker.history(period=period, interval="1d")
    dy_ticker.history(period=period, interval="1d")

    # bench cached reads
    yf_times = _bench(lambda: yf_ticker.history(period=period, interval="1d"), n)
    dy_times = _bench(lambda: dy_ticker.history(period=period, interval="1d"), n)

    yf_stats = _stats(yf_times)
    dy_stats = _stats(dy_times)

    return {"yfinance": yf_stats, "dyfinance": dy_stats}


# ── multi-ticker (download) benchmark ───────────────────────────────────────

def bench_download(tickers: list[str], period: str, n: int) -> dict:
    # warm-up
    yfinance.download(tickers, period=period, interval="1d")
    dyfinance.download(tickers, period=period, interval="1d")

    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)}


# ── pretty-print ─────────────────────────────────────────────────────────────

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{'=' * 60}")
    print(f"  {label}")
    print(f"{'=' * 60}")
    print(f"  {'':20s} {'yfinance (SQLite)':>20s} {'dyfinance (DuckDB)':>20s}")
    print(f"  {'median':20s} {yf['median_ms']:>17.2f} ms {dy['median_ms']:>17.2f} ms")
    print(f"  {'mean':20s} {yf['mean_ms']:>17.2f} ms {dy['mean_ms']:>17.2f} ms")
    print(f"  {'stdev':20s} {yf['stdev_ms']:>17.2f} ms {dy['stdev_ms']:>17.2f} ms")
    print(f"  {'min':20s} {yf['min_ms']:>17.2f} ms {dy['min_ms']:>17.2f} ms")
    print(f"  {'max':20s} {yf['max_ms']:>17.2f} ms {dy['max_ms']:>17.2f} ms")
    print(f"  {'─' * 58}")
    print(f"  speedup (median): {speedup:.2f}x", end="")
    if speedup >= 1:
        print("  ← dyfinance faster")
    else:
        print("  ← yfinance faster")


# ── main ─────────────────────────────────────────────────────────────────────

def main() -> None:
    parser = argparse.ArgumentParser(description="Cache read benchmark")
    parser.add_argument("-n", type=int, default=20, help="iterations (default 20)")
    parser.add_argument(
        "--tickers",
        nargs="+",
        default=["AAPL", "MSFT", "GOOGL"],
        help="tickers to benchmark",
    )
    parser.add_argument(
        "--periods",
        nargs="+",
        default=["1mo", "6mo", "1y"],
        help="periods to benchmark",
    )
    args = parser.parse_args()

    print(f"Benchmark: {args.n} iterations per scenario")
    print(f"Tickers : {args.tickers}")
    print(f"Periods : {args.periods}")

    # ── Single-ticker benchmarks ──
    for ticker in args.tickers:
        for period in args.periods:
            result = bench_ticker(ticker, period, args.n)
            _print_comparison(f"Ticker.history()  {ticker}  period={period}", result)

    # ── Multi-ticker download benchmark ──
    for period in args.periods:
        result = bench_download(args.tickers, period, args.n)
        _print_comparison(
            f"download({args.tickers})  period={period}",
            result,
        )


if __name__ == "__main__":
    main()
