"""
Performance analyzer for trading results

This module provides functionality to analyze trading performance,
calculate metrics, and generate comprehensive reports.
"""

import time
from typing import List, Optional
from openfund_agent.models.trading import Trade
from openfund_agent.models.performance import (
    TradeMetrics,
    PeriodMetrics,
    PerformanceReport,
)
from openfund_agent.feedback.metrics import (
    calculate_win_rate,
    calculate_profit_factor,
    calculate_max_drawdown,
    calculate_sharpe_ratio,
    calculate_sortino_ratio,
)
from openfund_agent.feedback.notification import NotificationService


class PerformanceAnalyzer:
    """
    Analyzes trading performance and generates reports

    This class provides methods to analyze individual trades,
    calculate period metrics, and generate comprehensive performance reports.
    """

    def __init__(self, notification_service: Optional[NotificationService] = None):
        """
        Initialize the performance analyzer

        Args:
            notification_service: Optional notification service for anomaly alerts
        """
        self.trade_history: List[Trade] = []
        self.notification_service = notification_service

    def analyze_trade(self, trade: Trade) -> TradeMetrics:
        """
        Analyze a single trade and calculate its metrics

        Args:
            trade: Trade record to analyze

        Returns:
            TradeMetrics object containing calculated metrics

        Raises:
            ValueError: If trade is not closed or missing required data
        """
        if trade.status != "closed":
            raise ValueError(f"Cannot analyze trade {trade.trade_id}: trade is not closed")

        if trade.exit_price is None or trade.exit_time is None:
            raise ValueError(f"Cannot analyze trade {trade.trade_id}: missing exit price or time")

        # Calculate PnL
        if trade.side == "long":
            pnl = (trade.exit_price - trade.entry_price) * trade.quantity
        else:  # short
            pnl = (trade.entry_price - trade.exit_price) * trade.quantity

        # Subtract fees
        pnl -= trade.fees

        # Calculate PnL percentage
        pnl_percent = pnl / (trade.entry_price * trade.quantity)

        # Calculate holding time
        holding_time = trade.exit_time - trade.entry_time

        return TradeMetrics(
            trade_id=trade.trade_id,
            symbol=trade.symbol,
            side=trade.side,
            entry_price=trade.entry_price,
            exit_price=trade.exit_price,
            quantity=trade.quantity,
            pnl=pnl,
            pnl_percent=pnl_percent,
            holding_time=holding_time,
            fees=trade.fees,
        )

    def analyze_period(self, start_time: int, end_time: int) -> PeriodMetrics:
        """
        Analyze performance for a specific time period

        Args:
            start_time: Period start timestamp (Unix time)
            end_time: Period end timestamp (Unix time)

        Returns:
            PeriodMetrics object containing calculated metrics for the period

        Raises:
            ValueError: If start_time >= end_time or no trades in period
        """
        if start_time >= end_time:
            raise ValueError("start_time must be less than end_time")

        # Filter trades in the period
        period_trades = [
            trade
            for trade in self.trade_history
            if trade.status == "closed"
            and trade.exit_time is not None
            and start_time <= trade.exit_time <= end_time
        ]

        if not period_trades:
            raise ValueError(f"No closed trades found in period {start_time} to {end_time}")

        # Analyze each trade
        trade_metrics = [self.analyze_trade(trade) for trade in period_trades]

        # Calculate aggregate metrics
        total_trades = len(trade_metrics)
        winning_trades = sum(1 for tm in trade_metrics if tm.pnl > 0)
        losing_trades = sum(1 for tm in trade_metrics if tm.pnl < 0)

        win_rate = calculate_win_rate(trade_metrics)
        profit_factor = calculate_profit_factor(trade_metrics)

        # Calculate average win/loss
        wins = [tm.pnl for tm in trade_metrics if tm.pnl > 0]
        losses = [tm.pnl for tm in trade_metrics if tm.pnl < 0]
        avg_win = sum(wins) / len(wins) if wins else 0.0
        avg_loss = sum(losses) / len(losses) if losses else 0.0

        # Calculate total PnL
        total_pnl = sum(tm.pnl for tm in trade_metrics)

        # Build equity curve
        equity_curve = self._build_equity_curve(trade_metrics)

        # Calculate drawdown
        max_drawdown = calculate_max_drawdown(equity_curve)

        # Calculate returns for Sharpe/Sortino
        returns = [tm.pnl_percent for tm in trade_metrics]
        sharpe_ratio = calculate_sharpe_ratio(returns)
        sortino_ratio = calculate_sortino_ratio(returns)

        return PeriodMetrics(
            total_trades=total_trades,
            winning_trades=winning_trades,
            losing_trades=losing_trades,
            win_rate=win_rate,
            total_pnl=total_pnl,
            avg_win=avg_win,
            avg_loss=avg_loss,
            profit_factor=profit_factor,
            max_drawdown=max_drawdown,
            sharpe_ratio=sharpe_ratio,
            sortino_ratio=sortino_ratio,
        )

    def generate_report(self, period: str = "daily") -> PerformanceReport:
        """
        Generate a comprehensive performance report

        Args:
            period: Report period ('daily' | 'weekly' | 'monthly')

        Returns:
            PerformanceReport object containing complete analysis

        Raises:
            ValueError: If period is invalid or no trades available
        """
        if period not in ["daily", "weekly", "monthly"]:
            raise ValueError(f"Invalid period: {period}. Must be 'daily', 'weekly', or 'monthly'")

        if not self.trade_history:
            raise ValueError("No trade history available to generate report")

        # Calculate time range based on period
        current_time = int(time.time())
        if period == "daily":
            start_time = current_time - 86400  # 24 hours
        elif period == "weekly":
            start_time = current_time - 604800  # 7 days
        else:  # monthly
            start_time = current_time - 2592000  # 30 days

        end_time = current_time

        # Analyze the period
        try:
            metrics = self.analyze_period(start_time, end_time)
        except ValueError as e:
            # If no trades in period, return empty report
            return PerformanceReport(
                period=period,
                start_time=start_time,
                end_time=end_time,
                metrics=PeriodMetrics(
                    total_trades=0,
                    winning_trades=0,
                    losing_trades=0,
                    win_rate=0.0,
                    total_pnl=0.0,
                    avg_win=0.0,
                    avg_loss=0.0,
                    profit_factor=0.0,
                    max_drawdown=0.0,
                    sharpe_ratio=0.0,
                    sortino_ratio=0.0,
                ),
                trade_list=[],
                equity_curve=[],
                anomalies=[f"No trades in period: {str(e)}"],
            )

        # Get trade metrics for the period
        period_trades = [
            trade
            for trade in self.trade_history
            if trade.status == "closed"
            and trade.exit_time is not None
            and start_time <= trade.exit_time <= end_time
        ]
        trade_list = [self.analyze_trade(trade) for trade in period_trades]

        # Build equity curve
        equity_curve = self._build_equity_curve(trade_list)

        # Detect anomalies
        anomalies = self._detect_anomalies(trade_list, metrics)

        # Send notification if anomalies detected
        if anomalies and self.notification_service:
            self.notification_service.send_anomaly_alert(
                anomalies=anomalies,
                context={
                    "period": period,
                    "metrics": metrics,
                    "timestamp": end_time,
                    "total_trades": len(trade_list),
                },
            )

        return PerformanceReport(
            period=period,
            start_time=start_time,
            end_time=end_time,
            metrics=metrics,
            trade_list=trade_list,
            equity_curve=equity_curve,
            anomalies=anomalies,
        )

    def _build_equity_curve(self, trade_metrics: List[TradeMetrics]) -> List[float]:
        """
        Build equity curve from trade metrics

        Args:
            trade_metrics: List of trade metrics

        Returns:
            List of cumulative equity values
        """
        if not trade_metrics:
            return []

        equity_curve = [0.0]
        cumulative_pnl = 0.0

        for tm in trade_metrics:
            cumulative_pnl += tm.pnl
            equity_curve.append(cumulative_pnl)

        return equity_curve

    def _detect_anomalies(
        self, trade_list: List[TradeMetrics], metrics: PeriodMetrics
    ) -> List[str]:
        """
        Detect anomalies in trading performance

        Args:
            trade_list: List of trade metrics
            metrics: Period metrics

        Returns:
            List of anomaly descriptions
        """
        anomalies = []

        # Check for extreme losses
        for trade in trade_list:
            if trade.pnl_percent < -0.10:  # Loss > 10%
                anomalies.append(
                    f"Extreme loss detected: Trade {trade.trade_id} lost {trade.pnl_percent*100:.2f}%"
                )

        # Check for extreme wins (possible data error)
        for trade in trade_list:
            if trade.pnl_percent > 1.0:  # Gain > 100%
                anomalies.append(
                    f"Extreme gain detected: Trade {trade.trade_id} gained {trade.pnl_percent*100:.2f}%"
                )

        # Check for very low win rate
        if metrics.win_rate < 0.30:
            anomalies.append(f"Low win rate: {metrics.win_rate*100:.2f}%")

        # Check for high drawdown
        if metrics.max_drawdown > 0.20:
            anomalies.append(f"High drawdown: {metrics.max_drawdown*100:.2f}%")

        # Check for negative Sharpe ratio
        if metrics.sharpe_ratio < 0:
            anomalies.append(f"Negative Sharpe ratio: {metrics.sharpe_ratio:.2f}")

        return anomalies
