"""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


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 _filter_by_selection(self, selection: "OptionSummary") -> pl.DataFrame:
        """Filter trades DataFrame based on OptionSummary selection."""
        df = self._trades

        # Use structured fields when available (blueprint/period options populate these)
        if (
            selection.strategy_name is not None
            or selection.blueprint_name is not None
            or selection.period is not None
        ):
            if selection.strategy_name:
                if "strategy_name" not in df.columns:
                    raise ValueError("strategy_name column required for strategy filter")
                df = df.filter(pl.col("strategy_name") == selection.strategy_name)
            if selection.blueprint_name:
                if "blueprint_name" not in df.columns:
                    raise ValueError("blueprint_name column required for blueprint filter")
                df = df.filter(pl.col("blueprint_name") == selection.blueprint_name)
            if selection.period:
                if "period" not in df.columns:
                    raise ValueError("period column required for period filter")
                df = df.filter(pl.col("period") == selection.period)
            return df

        # Legacy fallback: parse from value string
        value = str(selection.value)
        parts = [p.strip() for p in value.split("/")]

        if len(parts) >= 1 and parts[0]:
            if "strategy_name" not in df.columns:
                raise ValueError("strategy_name column required for strategy filter")
            df = df.filter(pl.col("strategy_name") == parts[0])

        if len(parts) >= 2 and parts[1]:
            if "blueprint_name" not in df.columns:
                raise ValueError("blueprint_name column required for blueprint filter")
            df = df.filter(pl.col("blueprint_name") == parts[1])

        if len(parts) >= 3 and parts[2]:
            if "period" not in df.columns:
                raise ValueError("period column required for period filter")
            df = df.filter(pl.col("period") == parts[2])

        return df

    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

        # Filter trades if selection provided (filtering happens at analyzer level)
        trades = self._filter_by_selection(selection) if selection else self._trades

        # Pass filtered trades to scope
        return scope_module.build_scope(
            trades=trades,
            metric_col=metric_col,
            normalize=normalize,
            config=self._config,
        )
