using Managing.Domain.Backtests;
using static Managing.Common.Enums;

public class BacktestScorer
{
    // Updated weights with more emphasis on HODL comparison and risk management
    private static readonly Dictionary<string, double> Weights = new Dictionary<string, double>
    {
        { "GrowthPercentage", 0.20 },
        { "SharpeRatio", 0.15 },
        { "HodlComparison", 0.10 }, // Increased from 0.05 to 0.15
        { "WinRate", 0.12 },
        { "ProfitabilityBonus", 0.08 },
        { "TradeCount", 0.05 },
        { "RecoveryTime", 0.02 },
        { "TestDuration", 0.03 },
        { "FeesImpact", 0.02 },
        { "RiskAdjustedReturn", 0.23 } // Increased from 0.08 to 0.23 (absorbed MaxDrawdownUsd weight)
    };

    public static double CalculateTotalScore(BacktestScoringParams p)
    {
        var result = CalculateDetailedScore(p);
        return result.Score;
    }

    public static BacktestScoringResult CalculateDetailedScore(BacktestScoringParams p)
    {
        var result = new BacktestScoringResult(0);

        try
        {
            // Early exit for no positions
            if (p.TradeCount == 0)
            {
                result.AddEarlyExitCheck("No Trades", "No trading positions were taken during the backtest period. A strategy must execute trades to be evaluated.");
                return result;
            }

            // Early exit for negative PnL - should be 0 score
            if (p.TotalPnL <= 0)
            {
                result.AddEarlyExitCheck("Negative PnL", $"Total profit/loss is negative (${p.TotalPnL:F2}). Only profitable strategies receive scores.");
                return result;
            }

            // Early exit if strategy significantly underperforms HODL (more than 2% worse)
            if (p.GrowthPercentage < p.HodlPercentage - 2)
            {
                result.AddEarlyExitCheck("HODL Underperformance", $"Strategy growth ({p.GrowthPercentage:F2}%) significantly underperforms HODL ({p.HodlPercentage:F2}%) by more than 2%. Buy-and-hold would have been more profitable.");
                return result;
            }

            var baseScore = CalculateBaseScore(p, result);
            var finalScore = ApplyProfitabilityRules(baseScore, p, result);

            result.Score = double.Clamp(finalScore, 0, 100);
            result.SummaryMessage = result.GenerateSummaryMessage();

            return result;
        }
        catch (Exception ex)
        {
            result.AddEarlyExitCheck("Calculation Error", $"An error occurred during score calculation: {ex.Message}");
            return result;
        }
    }

    private static double CalculateBaseScore(BacktestScoringParams p, BacktestScoringResult result)
    {
        var componentScores = new Dictionary<string, double>
        {
            { "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage, result) },
            { "SharpeRatio", CalculateSharpeScore(p.SharpeRatio, result) },
            { "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage, result) },
            { "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount, result) },
            { "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage, result) },
            { "TradeCount", CalculateTradeCountScore(p.TradeCount, result) },
            { "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime, p.Timeframe, result) },
            { "TestDuration", CalculateTestDurationScore(p.StartDate, p.EndDate, p.Timeframe, result) },
            { "FeesImpact", CalculateFeesImpactScore(p.Fees, p.TotalPnL, result) },
            { "RiskAdjustedReturn", CalculateRiskAdjustedReturnScore(p.TotalPnL, p.MaxDrawdown, p.TradingBalance, result) }
        };

        var totalScore = componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]);
        
        // Add component scores to result
        foreach (var kvp in componentScores)
        {
            var passed = kvp.Value > 0;
            result.AddCheck(kvp.Key, kvp.Value, Weights[kvp.Key], GetComponentMessage(kvp.Key, kvp.Value, p), passed);
        }

        return totalScore;
    }

    private static string GetComponentMessage(string component, double score, BacktestScoringParams p)
    {
        return component switch
        {
            "GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 20% for full score, 5% for 20 points, 10% for 50 points)",
            "SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio * 100:F2}, target: 4.0 for full score",
            "HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%, target: +5% for full score)",
            "WinRate" => $"Win rate of {p.WinRate:F2}% with {p.TradeCount} trades (significance factor: {Math.Min(1, (p.TradeCount - 5) / 50.0):F2})",
            "ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}% (max 50 points)",
            "TradeCount" => $"{p.TradeCount} trades executed (5-10: 0-50 points, 10-50: 50-100 points, 50+: 100 points)",
            "RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days (timeframe-adjusted max: {GetMaxRecoveryDays(p.Timeframe):F1} days)",
            "TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days (min: {GetMinTestDays(p.Timeframe):F1}, optimal: {GetMinTestDays(p.Timeframe) * 3:F1} days)",
            "FeesImpact" => $"Fees: ${p.Fees:F2} ({(p.TotalPnL > 0 ? p.Fees / p.TotalPnL * 100 : 0):F2}% of PnL, target: below 10% for full score)",
            "RiskAdjustedReturn" => $"Risk/Reward: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1 (PnL: ${p.TotalPnL:F2}, MaxDD: ${p.MaxDrawdown:F2}, target: >3:1 for full score)",
            _ => $"Component score: {score:F1}"
        };
    }

    private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p, BacktestScoringResult result)
    {
        var penaltyMultiplier = 1.0;

        // 1. Negative Growth Penalty (Dynamic) - only for positive PnL but negative growth
        if (p.GrowthPercentage < 0)
        {
            var negativePenalty = Math.Abs(p.GrowthPercentage) * 0.1; // 10% penalty per 1% loss
            var newMultiplier = Math.Max(0.1, 1 - negativePenalty);
            penaltyMultiplier *= newMultiplier;
            result.AddPenaltyCheck("Negative Growth", newMultiplier, $"Negative growth of {p.GrowthPercentage:F2}% applied {negativePenalty:F1}% penalty");
        }

        // 3. Win Rate Validation (Dynamic)
        if (p.WinRate < 30 && p.TradeCount > 10) // winRate is percentage, so 30 = 30%
        {
            var winRatePenalty = (30 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
            var newMultiplier = Math.Max(0.2, 1 - winRatePenalty);
            penaltyMultiplier *= newMultiplier;
            result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate:F2}% below 30% threshold applied {winRatePenalty:F1}% penalty");
        }

        // 4. Minimum Profit Threshold (Dynamic)
        if (p.GrowthPercentage < 2 && p.TradeCount > 5)
        {
            var profitPenalty = (2 - p.GrowthPercentage) * 0.1; // 10% penalty per 1% below 2%
            var newMultiplier = Math.Max(0.5, 1 - profitPenalty);
            penaltyMultiplier *= newMultiplier;
            result.AddPenaltyCheck("Low Profit", newMultiplier, $"Growth of {p.GrowthPercentage:F2}% below 2% minimum applied {profitPenalty:F1}% penalty");
        }

        // 5. Drawdown Penalty (Dynamic) - Enhanced to consider PnL ratio
        if (p.TradingBalance > 0 && p.MaxDrawdown > 0)
        {
            var drawdownPercentage = (double)(p.MaxDrawdown / p.TradingBalance * 100);
            if (drawdownPercentage > 20)
            {
                var drawdownPenalty = (drawdownPercentage - 20) * 0.02; // 2% penalty per 1% above 20%
                var newMultiplier = Math.Max(0.3, 1 - drawdownPenalty);
                penaltyMultiplier *= newMultiplier;
                result.AddPenaltyCheck("High Drawdown", newMultiplier, $"Drawdown of {drawdownPercentage:F1}% above 20% threshold applied {drawdownPenalty:F1}% penalty");
            }
        }

        // 6. Enhanced Drawdown vs PnL Penalty
        if (p.TotalPnL > 0 && p.MaxDrawdown > 0)
        {
            var drawdownToPnLRatio = (double)(p.MaxDrawdown / (decimal)p.TotalPnL);
            if (drawdownToPnLRatio > 1.5) // If drawdown is more than 150% of PnL
            {
                var ratioPenalty = (drawdownToPnLRatio - 1.5) * 0.2; // 20% penalty per 0.5 ratio above 1.5
                var newMultiplier = Math.Max(0.2, 1 - ratioPenalty);
                penaltyMultiplier *= newMultiplier;
                result.AddPenaltyCheck("Poor Risk/Reward", newMultiplier, $"Drawdown/PnL ratio of {drawdownToPnLRatio:F2}:1 above 1.5:1 threshold applied {ratioPenalty:F1}% penalty");
            }
        }

        // 7. Test Duration Penalty (Dynamic)
        var testDurationDays = (p.EndDate - p.StartDate).TotalDays;
        if (testDurationDays < 30)
        {
            var durationPenalty = (30 - testDurationDays) * 0.02; // 2% penalty per day below 30
            var newMultiplier = Math.Max(0.5, 1 - durationPenalty);
            penaltyMultiplier *= newMultiplier;
            result.AddPenaltyCheck("Short Test Duration", newMultiplier, $"Test duration of {testDurationDays:F1} days below 30-day minimum applied {durationPenalty:F1}% penalty");
        }

        // 8. HODL Underperformance Penalty (Dynamic)
        if (p.GrowthPercentage < p.HodlPercentage)
        {
            var hodlUnderperformance = p.HodlPercentage - p.GrowthPercentage;
            var hodlPenalty = hodlUnderperformance * 0.3; // 30% penalty per 1% underperformance
            var newMultiplier = Math.Max(0.1, 1 - hodlPenalty);
            penaltyMultiplier *= newMultiplier;
            result.AddPenaltyCheck("HODL Underperformance", newMultiplier, $"Underperforming HODL by {hodlUnderperformance:F2}% applied {hodlPenalty:F1}% penalty");
        }

        // 9. Money Management Risk/Reward Penalty (Dynamic)
        if (p.MoneyManagement != null && p.MoneyManagement.StopLoss > 0 && p.MoneyManagement.TakeProfit > 0)
        {
            var riskRewardRatio = (double)(p.MoneyManagement.TakeProfit / p.MoneyManagement.StopLoss);
            if (riskRewardRatio <= 1.0) // If Risk/Reward ratio is not above 1:1
            {
                var riskRewardPenalty = (1.0 - riskRewardRatio) * 0.5; // 50% penalty per 0.1 ratio below 1.0
                var newMultiplier = Math.Max(0.2, 1 - riskRewardPenalty);
                penaltyMultiplier *= newMultiplier;
                result.AddPenaltyCheck("Poor Risk/Reward Ratio", newMultiplier, $"Risk/Reward ratio of {riskRewardRatio:F2}:1 below 1:1 threshold applied {riskRewardPenalty:F1}% penalty");
            }
        }

        return baseScore * penaltyMultiplier;
    }

    private static double CalculateGrowthScore(double growthPercentage, BacktestScoringResult result)
    {
        // Negative growth always scores 0
        if (growthPercentage < 0)
        {
            return 0;
        }

        return growthPercentage switch
        {
            < 5 => growthPercentage * 4, // 2% → 8, 4% → 16
            < 10 => 20 + (growthPercentage - 5) * 6, // 5% → 20, 7% → 32, 9% → 44
            < 20 => 50 + (growthPercentage - 10) * 5, // 10% → 50, 15% → 75, 19% → 95
            _ => 100
        };
    }

    private static double GetNegativePnLMultiplier(double growthPercentage)
    {
        return growthPercentage switch
        {
            > -5 => 0.6,
            > -10 => 0.4,
            > -20 => 0.2,
            _ => 0.1
        };
    }

    private static double CalculateProfitabilityBonus(double growthPercentage, BacktestScoringResult result)
    {
        return growthPercentage switch
        {
            > 0 => 50 * (1 - 1 / (1 + growthPercentage / 30)), // Reduced max bonus to 50
            _ => 0
        };
    }

    private static double CalculateSharpeScore(double sharpeRatio, BacktestScoringResult result)
    {
        // Multiply Sharpe ratio by 100 for more accurate scoring
        var adjustedSharpeRatio = sharpeRatio * 100;
        
        return adjustedSharpeRatio switch
        {
            < 0 => 0,
            > 4 => 100, // Increased threshold from 3 to 4
            _ => (adjustedSharpeRatio / 4) * 100
        };
    }



    private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
    {
        // winRate is passed as percentage (e.g., 23.45 for 23.45%), convert to decimal for scoring
        var winRateDecimal = winRate / 100.0;
        var baseScore = winRateDecimal;
        
        // Significance factor - more aggressive
        var significanceFactor = Math.Min(1, (tradeCount - 5) / 50.0); // Start at 5 trades, full significance at 55 trades
        
        // Additional penalty for very few trades
        if (tradeCount < 10)
        {
            significanceFactor *= 0.5; // 50% penalty for less than 10 trades
        }
        
        return baseScore * significanceFactor;
    }

    private static double CalculateHodlComparisonScore(double strategyGrowth, double hodlGrowth, BacktestScoringResult result)
    {
        var difference = strategyGrowth - hodlGrowth;
        
        // Much more aggressive scoring for HODL comparison
        return difference switch
        {
            > 5 => 100, // Significantly outperform HODL (>5% better)
            > 2 => 80 + (difference - 2) * 6.67, // 2-5% better: 80-100 points
            > 0 => 40 + difference * 20, // 0-2% better: 40-80 points
            > -1 => 20 + (difference + 1) * 20, // -1-0%: 20-40 points
            > -2 => Math.Max(0, 10 + (difference + 2) * 10), // -2 to -1%: 0-20 points
            _ => 0 // More than 2% worse than HODL = 0 points (handled by early exit)
        };
    }

    private static double CalculateTradeCountScore(int tradeCount, BacktestScoringResult result)
    {
        return tradeCount switch
        {
            < 5 => 0,
            < 10 => (tradeCount - 5) * 10, // 5-10 trades: 0-50 points
            < 50 => 50 + (tradeCount - 10) * 1.25, // 10-50 trades: 50-100 points
            _ => 100
        };
    }

    private static double CalculateRecoveryScore(TimeSpan recoveryTime, Timeframe timeframe, BacktestScoringResult result)
    {
        var days = recoveryTime.TotalDays;
        
        // Adjust recovery expectations based on timeframe
        var maxRecoveryDays = timeframe switch
        {
            Timeframe.FiveMinutes => 3.0,      // 1 week for 5m
            Timeframe.FifteenMinutes => 5.0,  // 2 weeks for 15m
            Timeframe.ThirtyMinutes => 10.0,   // 3 weeks for 30m
            Timeframe.OneHour => 15.0,         // 1 month for 1h
            Timeframe.FourHour => 30.0,        // 2 months for 4h
            Timeframe.OneDay => 90.0,         // 6 months for 1d
            _ => 30.0                          // Default to 1 month
        };
        
        if (days < 0) return 100;
        if (days > maxRecoveryDays) return 0;
        return 100 - (days / maxRecoveryDays * 100);
    }

    private static double CalculateTestDurationScore(DateTime startDate, DateTime endDate, Timeframe timeframe, BacktestScoringResult result)
    {
        var durationDays = (endDate - startDate).TotalDays;
        
        // Adjust minimum test duration based on timeframe
        var minTestDays = timeframe switch
        {
            Timeframe.FiveMinutes => 14.0,      // 3 days for 5m
            Timeframe.FifteenMinutes => 28.0,   // 1 week for 15m
            Timeframe.ThirtyMinutes => 56.0,   // 2 weeks for 30m
            Timeframe.OneHour => 84.0,         // 3 weeks for 1h
            Timeframe.FourHour => 120.0,        // 1 month for 4h
            Timeframe.OneDay => 90.0,          // 3 months for 1d
            _ => 21.0                          // Default to 3 weeks
        };
        
        var optimalTestDays = minTestDays * 3; // Optimal is 3x minimum
        
        if (durationDays < minTestDays) return 0;
        if (durationDays < optimalTestDays) return (durationDays / optimalTestDays) * 100;
        return 100;
    }

    private static double CalculateFeesImpactScore(double fees, double totalPnL, BacktestScoringResult result)
    {
        if (totalPnL <= 0) return 0;
        
        var feesPercentage = fees / totalPnL * 100;
        
        // If fees are higher than PnL, heavy penalty
        if (fees > totalPnL)
        {
            return 0;
        }
        
        // Fee efficiency score based on PnL
        var feeEfficiency = feesPercentage switch
        {
            > 20 => 0, // More than 20% fees = 0
            > 10 => 50 - (feesPercentage - 10) * 5, // 10-20%: 50-0 points
            _ => 100 - feesPercentage * 5 // 0-10%: 100-50 points
        };
        
        return feeEfficiency;
    }

    private static double CalculateRiskAdjustedReturnScore(double totalPnL, decimal maxDrawdown, decimal tradingBalance, BacktestScoringResult result)
    {
        if (tradingBalance <= 0 || maxDrawdown <= 0) return 0;
        
        // Calculate drawdown as percentage of trading balance
        var drawdownPercentage = (double)(maxDrawdown / tradingBalance * 100);
        
        // Calculate PnL as percentage of trading balance
        var pnlPercentage = (double)((decimal)totalPnL / tradingBalance * 100);
        
        // Score based on PnL percentage vs drawdown percentage ratio
        var riskRewardRatio = pnlPercentage / drawdownPercentage;
        
        // Score based on risk-adjusted return (PnL vs Drawdown based on trading balance)
        return riskRewardRatio switch
        {
            > 3 => 100, // Excellent risk-adjusted return (>3:1)
            > 2 => 80 + (riskRewardRatio - 2) * 20, // 2-3:1: 80-100 points
            > 1.5 => 60 + (riskRewardRatio - 1.5) * 40, // 1.5-2:1: 60-80 points
            > 1 => 40 + (riskRewardRatio - 1) * 40, // 1-1.5:1: 40-60 points
            > 0.5 => 20 + (riskRewardRatio - 0.5) * 40, // 0.5-1:1: 20-40 points
            _ => Math.Max(0, riskRewardRatio * 40) // 0-0.5:1: 0-20 points
        };
    }

    // Helper methods for message generation
    private static double GetMaxRecoveryDays(Timeframe timeframe)
    {
        return timeframe switch
        {
            Timeframe.FiveMinutes => 3.0,
            Timeframe.FifteenMinutes => 5.0,
            Timeframe.ThirtyMinutes => 10.0,
            Timeframe.OneHour => 15.0,
            Timeframe.FourHour => 30.0,
            Timeframe.OneDay => 90.0,
            _ => 30.0
        };
    }

    private static double GetMinTestDays(Timeframe timeframe)
    {
        return timeframe switch
        {
            Timeframe.FiveMinutes => 14.0,
            Timeframe.FifteenMinutes => 28.0,
            Timeframe.ThirtyMinutes => 56.0,
            Timeframe.OneHour => 84.0,
            Timeframe.FourHour => 120.0,
            Timeframe.OneDay => 90.0,
            _ => 21.0
        };
    }
}