"""TradesAnalyzer - Core analysis class for hierarchical trades analysis."""

from __future__ import annotations

from typing import TYPE_CHECKING

import polars as pl

from ..mae_mfe.config import DEFAULT_CONFIG, ChartConfig
from .types import (
    REQUIRED_COLUMNS,
    AnalysisScope,
)

if TYPE_CHECKING:
    from .scope import AvailableOptions, OptionSummary, ScopeResult


def calculate_performance(
    df: pl.DataFrame,
    metric_col: str = "pnl",
) -> pl.DataFrame:
    """Calculate performance metrics matching Rust performance_calculator.

    Uses Polars expressions for efficient single-pass calculation.

    Args:
        df: DataFrame containing trade data.
        metric_col: Column to use for calculations ("pnl" or "pnl_pct").

    Returns:
        Single-row DataFrame with performance metrics.

    Raises:
        ValueError: If DataFrame is empty or metric_col not found.
    """
    n = len(df)

    if n == 0:
        raise ValueError("Cannot compute performance on empty DataFrame")

    if metric_col not in df.columns:
        raise ValueError(f"Column '{metric_col}' not found in DataFrame")

    # Define win/lose conditions
    win_trades = pl.col(metric_col) > 0
    lose_trades = pl.col(metric_col) <= 0

    # Step 1: Calculate base metrics
    base_exprs = [
        pl.len().cast(pl.UInt32).alias("trades"),
        (win_trades.cast(pl.UInt32).sum().cast(pl.Float64) / pl.len().cast(pl.Float64)).alias(
            "win_ratio"
        ),
    ]
    df_with_base = df.with_columns(base_exprs)

    # Step 2: Build all performance expressions
    exprs = [
        # Trade statistics
        pl.col("trades").first().alias("trades"),
    ]

    # Add time columns if available
    if "entry_time" in df.columns:
        exprs.extend(
            [
                pl.col("entry_time").min().alias("first_entry_time"),
                pl.col("entry_time").max().alias("last_entry_time"),
            ]
        )
    if "exit_time" in df.columns:
        exprs.append(pl.col("exit_time").max().alias("last_exit_time"))

    # Profitability indicators
    exprs.extend(
        [
            pl.col("win_ratio").first().alias("win_ratio"),
            # profit_factor: total_wins / abs(total_losses)
            (
                pl.col(metric_col).filter(win_trades).sum()
                / pl.col(metric_col).filter(lose_trades).sum().abs()
            ).alias("profit_factor"),
            # expect_payoff: avg_win * win_ratio + avg_loss * (1 - win_ratio)
            (
                pl.col(metric_col).filter(win_trades).mean() * pl.col("win_ratio").first()
                + pl.col(metric_col).filter(lose_trades).mean()
                * (pl.lit(1.0) - pl.col("win_ratio").first())
            ).alias("expect_payoff"),
        ]
    )

    # Risk management indicators
    exprs.extend(
        [
            # recovery_factor: total / max_drawdown
            (
                pl.col(metric_col).cum_sum().last()
                / (pl.col(metric_col).cum_sum().cum_max() - pl.col(metric_col).cum_sum()).max()
            ).alias("recovery_factor"),
            # sortino_ratio: mean / downside_std
            (pl.col(metric_col).mean() / pl.col(metric_col).filter(lose_trades).std()).alias(
                "sortino_ratio"
            ),
        ]
    )

    # MAE risk estimates (if columns exist)
    if "mae" in df.columns and "entry_price" in df.columns:
        mae_pct = pl.col("mae") / pl.col("entry_price")
        exprs.extend(
            [
                mae_pct.quantile(0.75, interpolation="linear").alias("risk_mae_pct_p75"),
                pl.when((mae_pct.median() * 2.0).gt(mae_pct.max()))
                .then(mae_pct.median() * 2.0)
                .otherwise(mae_pct.max())
                .alias("risk_mae_pct_conservative"),
                mae_pct.quantile(0.95, interpolation="linear").alias("risk_mae_pct_p95"),
            ]
        )

    # Overall performance
    exprs.extend(
        [
            pl.col(metric_col).sum().alias("total"),
            pl.col(metric_col).std().alias("pnl_std"),
        ]
    )

    # Advanced analysis: g_mfe_after_mae_pct (if columns exist)
    if "g_mfe_idx" in df.columns and "mae_idx" in df.columns and "pnl_pct" in df.columns:
        exprs.append(
            (
                pl.col("g_mfe_idx")
                .gt(pl.col("mae_idx"))
                .and_(pl.col("pnl_pct").gt(0))
                .cast(pl.UInt32)
                .sum()
                .cast(pl.Float64)
                / pl.len().cast(pl.Float64)
            ).alias("g_mfe_after_mae_pct")
        )

    # Execute expressions
    result = df_with_base.select(exprs)

    # Step 3: Calculate derived metrics (kelly, sqn)
    return result.with_columns(
        [
            # kelly: win_ratio - (1 - win_ratio) / expect_payoff
            (
                pl.col("win_ratio")
                - ((pl.lit(1.0) - pl.col("win_ratio")) / pl.col("expect_payoff"))
            ).alias("kelly"),
            # sqn: expect_payoff * sqrt(min(trades, 100)) / pnl_std
            (
                pl.col("expect_payoff")
                * pl.when(pl.col("trades").cast(pl.Float64).lt(100.0))
                .then(pl.col("trades").cast(pl.Float64))
                .otherwise(pl.lit(100.0))
                .sqrt()
                / pl.col("pnl_std")
            ).alias("sqn"),
        ]
    )


class TradesAnalyzer:
    """Trades analysis with hierarchical scoping.

    This class provides exploration of available data and builds scopes for
    MAE/MFE workflow analysis.

    Attributes:
        trades: The trades DataFrame.
        n_trades: Number of trades.
        config: Chart configuration.

    Example:
        >>> from tradepose_client.analysis import TradesAnalyzer
        >>>
        >>> # Initialize (from BatchResults)
        >>> analyzer = batch.analyzer()
        >>>
        >>> # Explore available options
        >>> blueprints = analyzer.available_blueprints()
        >>> blueprints.to_dataframe()
        >>>
        >>> # Build scope for analysis
        >>> scope = analyzer.build_scope(blueprints.options[0])
        >>>
        >>> # Analyze
        >>> scope.performance()
        >>> scope.plot_scatter(x="mae", y="mfe")
        >>> scope.full_workflow().dashboard()
    """

    _trades: pl.DataFrame
    _scope: AnalysisScope
    _config: ChartConfig

    def __init__(
        self,
        trades: pl.DataFrame,
        *,
        scope: AnalysisScope = "batch",
        config: ChartConfig | None = None,
    ) -> None:
        """Initialize TradesAnalyzer.

        Args:
            trades: Polars DataFrame with trades data.
            scope: Analysis scope level (batch, strategy, blueprint, trade).
            config: Optional chart configuration.

        Raises:
            ValueError: If required columns are missing.
        """
        self._validate_columns(trades)
        self._trades = trades
        self._scope = scope
        self._config = config or DEFAULT_CONFIG

    @staticmethod
    def _validate_columns(trades: pl.DataFrame) -> None:
        """Validate that required columns are present."""
        columns = set(trades.columns)
        missing = REQUIRED_COLUMNS - columns
        if missing:
            raise ValueError(
                f"Missing required columns: {missing}. Required columns are: {REQUIRED_COLUMNS}"
            )

    @property
    def trades(self) -> pl.DataFrame:
        """Get the trades DataFrame."""
        return self._trades

    @property
    def n_trades(self) -> int:
        """Get the number of trades."""
        return len(self._trades)

    @property
    def scope(self) -> AnalysisScope:
        """Get the current analysis scope."""
        return self._scope

    @property
    def config(self) -> ChartConfig:
        """Get the chart configuration."""
        return self._config

    # =========================================================================
    # Scope Exploration
    # =========================================================================

    def available_strategies(self) -> "AvailableOptions":
        """List available strategies with statistics.

        Returns:
            AvailableOptions with strategy breakdown.

        Example:
            >>> strategies = analyzer.available_strategies()
            >>> strategies.to_dataframe()
        """
        from . import scope as scope_module

        return scope_module.available_strategies(self._trades)

    def available_blueprints(self) -> "AvailableOptions":
        """List available blueprints with statistics.

        Returns:
            AvailableOptions with blueprint breakdown (key: "strategy / blueprint").

        Example:
            >>> blueprints = analyzer.available_blueprints()
            >>> blueprints.to_dataframe()
        """
        from . import scope as scope_module

        return scope_module.available_blueprints(self._trades)

    def available_periods(self) -> "AvailableOptions":
        """List available time periods with statistics.

        Returns:
            AvailableOptions with period breakdown (key: "strategy / blueprint / period").

        Example:
            >>> periods = analyzer.available_periods()
            >>> periods.to_dataframe()
        """
        from . import scope as scope_module

        return scope_module.available_periods(self._trades)

    # =========================================================================
    # Scope Builder
    # =========================================================================

    def build_scope(
        self,
        selection: "OptionSummary | None" = None,
        metric_col: str = "pnl_pct",
        normalize: bool = True,
    ) -> "ScopeResult":
        """Build a scope for analysis.

        Args:
            selection: Optional OptionSummary from available_*() methods.
                       If provided, filters data to match selection.
            metric_col: Default metric column for performance/plot methods.
                        Options: "pnl" (absolute) or "pnl_pct" (percentage).
                        Default: "pnl_pct" (percentage-based analysis).
            normalize: Default normalization for scatter plots.
                       If True, "mae" → pl.col("mae") / pl.col("mae_volatility").
                       Default: True (users typically want normalized distribution).

        Returns:
            ScopeResult with filtered trades and analysis methods.

        Example:
            >>> # Default uses pnl_pct
            >>> scope = analyzer.build_scope(opt)
            >>>
            >>> # All methods use defaults
            >>> scope.performance()           # uses pnl_pct
            >>> scope.plot_equity()           # uses pnl_pct
            >>> scope.plot_scatter()          # normalized by volatility
            >>>
            >>> # Override per-call
            >>> scope.plot_scatter(normalize=False)  # raw values
        """
        from . import scope as scope_module

        if selection is not None:
            config = scope_module._parse_selection(selection)
        else:
            config = scope_module.ScopeConfig()

        return scope_module.ScopeResult(self, config, metric_col, normalize)
