"""Cached drop-in replacement for yfinance.download()."""

from __future__ import annotations

from datetime import datetime
from typing import Any

import pandas as pd

from dyfinance._constants import CACHEABLE_INTERVALS


def download(
    tickers: str | list[str],
    start: str | datetime | None = None,
    end: str | datetime | None = None,
    actions: bool = False,
    threads: bool | int = True,
    ignore_tz: bool | None = None,
    group_by: str = "column",
    auto_adjust: bool = True,
    back_adjust: bool = False,
    repair: bool = False,
    keepna: bool = False,
    progress: bool = True,
    period: str | None = None,
    interval: str = "1d",
    prepost: bool = False,
    rounding: bool = False,
    timeout: float = 10,
    session: Any = None,
    multi_level_index: bool = True,
) -> pd.DataFrame | None:
    """Cached drop-in replacement for yfinance.download().

    For cacheable daily intervals, fetches per-ticker via dyfinance.Ticker.history()
    to leverage the DuckDB cache. For intraday or non-cacheable configurations,
    falls through to yfinance.download() directly.
    """
    if interval.lower() not in CACHEABLE_INTERVALS:
        import yfinance

        result: pd.DataFrame = yfinance.download(
            tickers, start=start, end=end, actions=actions, threads=threads,
            ignore_tz=ignore_tz, group_by=group_by, auto_adjust=auto_adjust,
            back_adjust=back_adjust, repair=repair, keepna=keepna,
            progress=progress, period=period, interval=interval,
            prepost=prepost, rounding=rounding, timeout=timeout,
            session=session, multi_level_index=multi_level_index,
        )
        return result

    # Normalize ticker list
    if isinstance(tickers, str):
        ticker_list = tickers.replace(",", " ").split()
    elif isinstance(tickers, (list, set, tuple)):
        ticker_list = list(tickers)
    else:
        ticker_list = [str(tickers)]

    # Deduplicate while preserving input order (matches yfinance behavior)
    seen: set[str] = set()
    deduped: list[str] = []
    for t in ticker_list:
        t_upper = t.upper()
        if t_upper not in seen:
            seen.add(t_upper)
            deduped.append(t_upper)
    ticker_list = deduped

    from dyfinance._ticker import Ticker

    dfs: dict[str, pd.DataFrame] = {}

    if len(ticker_list) == 1:
        # Single ticker: delegate to Ticker.history() directly to avoid
        # redundant batch cache query overhead.
        tkr = Ticker(ticker_list[0], session=session)
        df = tkr.history(
            period=period, interval=interval, start=start, end=end,
            prepost=prepost, actions=actions, auto_adjust=auto_adjust,
            back_adjust=back_adjust, repair=repair, keepna=keepna,
            rounding=rounding, timeout=timeout,
        )
        dfs[ticker_list[0]] = df
    else:
        # Multiple tickers: batch cache read for fully-cached tickers,
        # per-ticker fallback for cache misses.
        from dyfinance._cache import CacheEngine

        cache = CacheEngine.default()
        resolved_start, resolved_end = Ticker._resolve_date_range(period, start, end)

        cached_dfs, uncovered = cache.get_cached_ohlcv_batch(
            ticker_list, interval, resolved_start, resolved_end,
        )

        for sym, raw_df in cached_dfs.items():
            if not raw_df.empty:
                dfs[sym] = Ticker._apply_post_processing(
                    raw_df,
                    actions=actions,
                    auto_adjust=auto_adjust,
                    back_adjust=back_adjust,
                    keepna=keepna,
                    rounding=rounding,
                )
            else:
                dfs[sym] = raw_df

        for symbol in uncovered:
            tkr = Ticker(symbol, session=session)
            df = tkr.history(
                period=period, interval=interval, start=start, end=end,
                prepost=prepost, actions=actions, auto_adjust=auto_adjust,
                back_adjust=back_adjust, repair=repair, keepna=keepna,
                rounding=rounding, timeout=timeout,
            )
            dfs[symbol] = df

    if ignore_tz is None:
        ignore_tz = interval[-1] not in ("m", "h")

    if ignore_tz:
        for sym in dfs:
            if dfs[sym] is not None and not dfs[sym].empty:
                idx = dfs[sym].index
                if isinstance(idx, pd.DatetimeIndex) and idx.tz is not None:
                    dfs[sym].index = idx.tz_localize(None)
    else:
        # yfinance.download() returns UTC when ignore_tz=False;
        # Ticker.history() returns exchange TZ, so convert to UTC.
        for sym in dfs:
            if dfs[sym] is not None and not dfs[sym].empty:
                idx = dfs[sym].index
                if isinstance(idx, pd.DatetimeIndex) and idx.tz is not None:
                    dfs[sym].index = idx.tz_convert("UTC")

    if len(ticker_list) == 1 and not multi_level_index:
        result = dfs[ticker_list[0]]
        result = result.reindex(columns=sorted(result.columns))
        result.index.name = "Date"
        return result

    data = pd.concat(
        dfs.values(), axis=1, sort=True,
        keys=dfs.keys(), names=["Ticker", "Price"],
    )

    if group_by == "column" and isinstance(data.columns, pd.MultiIndex):
        data.columns = data.columns.swaplevel(0, 1)
        data = data.sort_index(level=0, axis=1)

    data.index.name = "Date"
    return data
