"""Arrow-to-DataFrame conversion utilities.

Supports pandas and polars with automatic backend detection.
All conversions are zero-copy where possible (via PyArrow).
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional, Sequence, Union


def detect_backend() -> str:
    """Auto-detect the best available DataFrame backend.

    Returns:
        ``"pandas"``, ``"polars"``, or ``"arrow"`` (fallback).
    """
    try:
        import pandas  # noqa: F401
        return "pandas"
    except ImportError:
        pass
    try:
        import polars  # noqa: F401
        return "polars"
    except ImportError:
        pass
    return "arrow"


def _resolve_backend(backend: str) -> str:
    if backend == "auto":
        return detect_backend()
    return backend


def arrow_to_df(
    batch: Any,
    backend: str = "auto",
) -> Any:
    """Convert a PyArrow RecordBatch or Table to a DataFrame.

    Args:
        batch: A ``pyarrow.RecordBatch`` or ``pyarrow.Table``.
        backend: ``"pandas"``, ``"polars"``, or ``"auto"`` (detect).

    Returns:
        A pandas DataFrame or polars DataFrame.

    Raises:
        ImportError: If the requested backend is not installed.
    """
    backend = _resolve_backend(backend)

    if backend == "pandas":
        import pandas as pd
        import pyarrow as pa

        if isinstance(batch, pa.RecordBatch):
            batch = pa.Table.from_batches([batch])
        return batch.to_pandas()

    if backend == "polars":
        import polars as pl
        import pyarrow as pa

        if isinstance(batch, pa.RecordBatch):
            batch = pa.Table.from_batches([batch])
        return pl.from_arrow(batch)

    # Fallback: return as-is
    return batch


def arrow_to_series(
    array: Any,
    name: str = "value",
    backend: str = "auto",
) -> Any:
    """Convert a PyArrow Array to a pandas Series or polars Series.

    Args:
        array: A ``pyarrow.Array``, ``pyarrow.ChunkedArray``, or ``pyarrow.Float64Array``.
        name: Name for the resulting Series.
        backend: ``"pandas"``, ``"polars"``, or ``"auto"`` (detect).

    Returns:
        A pandas Series or polars Series.
    """
    backend = _resolve_backend(backend)

    if backend == "pandas":
        import pandas as pd

        if hasattr(array, "to_pandas"):
            return pd.Series(array.to_pandas(), name=name)
        return pd.Series(array, name=name)

    if backend == "polars":
        import polars as pl

        if hasattr(array, "to_pylist"):
            return pl.Series(name=name, values=array.to_pylist())
        return pl.Series(name=name, values=list(array))

    return array


def results_to_df(
    results: Sequence[Any],
    param_grid: Optional[Dict[str, List[Any]]] = None,
    backend: str = "auto",
) -> Any:
    """Convert a list of BacktestResult (or Result) objects to a metrics DataFrame.

    Each row contains all performance metrics plus parameter values (if provided).

    Args:
        results: Sequence of BacktestResult or Result objects.
        param_grid: Optional parameter grid dict (used to label rows with param values).
            When provided, the Cartesian product is expanded to match result order.
        backend: ``"pandas"``, ``"polars"``, or ``"auto"``.

    Returns:
        A DataFrame with one row per result and columns for each metric + parameter.
    """
    import itertools

    backend = _resolve_backend(backend)

    rows: List[Dict[str, Any]] = []

    # Expand parameter grid into list of param dicts
    param_combos: Optional[List[Dict[str, Any]]] = None
    if param_grid:
        keys = list(param_grid.keys())
        values = [param_grid[k] for k in keys]
        param_combos = [dict(zip(keys, combo)) for combo in itertools.product(*values)]

    for i, result in enumerate(results):
        # Support both raw BacktestResult and Result wrapper
        metrics = result.metrics if hasattr(result, "metrics") else {}
        row: Dict[str, Any] = {}

        # Add parameters
        if param_combos is not None and i < len(param_combos):
            for k, v in param_combos[i].items():
                row[f"param_{k}"] = v

        # Flatten metrics dict
        if isinstance(metrics, dict):
            for k, v in metrics.items():
                if isinstance(v, dict):
                    # Nested (e.g. trade_stats)
                    for sub_k, sub_v in v.items():
                        row[sub_k] = sub_v
                else:
                    row[k] = v

        rows.append(row)

    if backend == "pandas":
        import pandas as pd
        return pd.DataFrame(rows)

    if backend == "polars":
        import polars as pl
        return pl.DataFrame(rows)

    return rows
