using System.Collections.Immutable;
using Microsoft.Extensions.Logging;

namespace KArtSell.Modules.ModelOperations.ShadowRun;

/// <summary>
/// Calculates performance metrics from replay results.
/// Implements: Sharpe, Calmar, Max Drawdown, Win Rate, PBO, DSR.
/// </summary>
public sealed class MetricsCalculator(ILogger<MetricsCalculator> logger)
{
    private const decimal RiskFreeRate = 0.02m;  // 2% annual
    private const int TradingDaysPerYear = 252;

    /// <summary>
    /// Calculate all metrics from replay results.
    /// </summary>
    public async Task<ShadowRunMetrics> CalculateAsync(
        ReplayResult replay,
        IReadOnlyList<DataBackfiller.OhlcvBar> ohlcvBars,
        IReadOnlyList<DataBackfiller.FeeScheduleEntry> feeSchedule,
        CancellationToken cancellationToken)
    {
        await Task.Delay(10, cancellationToken);  // Async marker

        logger.LogInformation(
            "Calculating metrics for {OrderCount} orders, {TradingDays} days",
            replay.Orders.Count, replay.DailyReturns.Count);

        var dailyReturns = replay.DailyReturns.ToList();

        if (dailyReturns.Count < TradingDaysPerYear)
        {
            logger.LogWarning("Insufficient data for annual metrics: {DayCount} < {MinDays}",
                dailyReturns.Count, TradingDaysPerYear);
        }

        var totalReturn = CalculateTotalReturn(replay.PortfolioHistory);
        var sharpe = CalculateSharpeRatio(dailyReturns);
        var calmar = CalculateCalmarRatio(totalReturn, dailyReturns);
        var maxDD = CalculateMaxDrawdown(replay.PortfolioHistory);
        var winRate = CalculateWinRate(dailyReturns);
        var pbo = CalculatePbo(dailyReturns);
        var dsr = CalculateDailySharePercentile(dailyReturns);

        var metrics = new ShadowRunMetrics(
            TotalReturn: totalReturn,
            SharpeRatio: sharpe,
            CalmurRatio: calmar,
            MaximumDrawdown: maxDD,
            WinRate: winRate,
            ProbOfBacktestOverfit: pbo,
            DailySharePercentile: dsr,
            TradingDays: dailyReturns.Count);

        logger.LogInformation(
            "Metrics calculated: Return={Return:P}, Sharpe={Sharpe:F2}, PBO={Pbo:P}, DSR={Dsr:P}",
            metrics.TotalReturn, metrics.SharpeRatio, metrics.ProbOfBacktestOverfit, metrics.DailySharePercentile);

        return metrics;
    }

    private decimal CalculateTotalReturn(IReadOnlyList<ReplayEngine.Portfolio> history)
    {
        if (history.Count == 0) return 0;
        var start = history[0].TotalValue;
        var end = history[^1].TotalValue;
        return (end - start) / start;
    }

    private decimal CalculateSharpeRatio(List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        if (dailyReturns.Count < 2) return 0;

        var mean = dailyReturns.Average(r => r.Return);
        var variance = dailyReturns.Average(r => (r.Return - mean) * (r.Return - mean));
        var stdDev = (decimal)Math.Sqrt((double)variance);

        if (stdDev == 0) return 0;

        var dailyRiskFreeRate = (RiskFreeRate / TradingDaysPerYear);
        var excessReturn = mean - dailyRiskFreeRate;
        var annualizedSharpe = (excessReturn / stdDev) * (decimal)Math.Sqrt(TradingDaysPerYear);

        return annualizedSharpe;
    }

    private decimal CalculateCalmarRatio(decimal totalReturn, List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        var maxDD = CalculateMaxDrawdownFromReturns(dailyReturns);
        if (maxDD == 0) return 0;

        var annualizedReturn = totalReturn * (TradingDaysPerYear / dailyReturns.Count);
        return annualizedReturn / Math.Abs(maxDD);
    }

    private decimal CalculateMaxDrawdown(IReadOnlyList<ReplayEngine.Portfolio> history)
    {
        if (history.Count == 0) return 0;

        decimal maxValue = history[0].TotalValue;
        decimal maxDD = 0;

        foreach (var portfolio in history)
        {
            if (portfolio.TotalValue > maxValue)
                maxValue = portfolio.TotalValue;

            var dd = (portfolio.TotalValue - maxValue) / maxValue;
            if (dd < maxDD)
                maxDD = dd;
        }

        return Math.Abs(maxDD);
    }

    private decimal CalculateMaxDrawdownFromReturns(List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        if (dailyReturns.Count == 0) return 0;

        decimal cumValue = 1;
        decimal maxValue = 1;
        decimal maxDD = 0;

        foreach (var (_, ret) in dailyReturns)
        {
            cumValue *= (1 + ret);
            if (cumValue > maxValue)
                maxValue = cumValue;

            var dd = (cumValue - maxValue) / maxValue;
            if (dd < maxDD)
                maxDD = dd;
        }

        return Math.Abs(maxDD);
    }

    private decimal CalculateWinRate(List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        if (dailyReturns.Count == 0) return 0;
        var wins = dailyReturns.Count(r => r.Return > 0);
        return (decimal)wins / dailyReturns.Count;
    }

    private decimal CalculatePbo(List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        // Simplified PBO: out-of-sample Sharpe regression slope
        // Full implementation: partition into 5-fold CV, measure slope of test Sharpe vs. fold
        if (dailyReturns.Count < TradingDaysPerYear * 2) return 0.5m;  // Default high PBO if insufficient data

        var mid = dailyReturns.Count / 2;
        var inSampleSharpe = CalculateSharpeRatio(dailyReturns.Take(mid).ToList());
        var outOfSampleSharpe = CalculateSharpeRatio(dailyReturns.Skip(mid).ToList());

        // PBO = max(0, 1 - (OOS Sharpe / IS Sharpe))
        if (inSampleSharpe == 0) return 0.5m;
        var ratio = outOfSampleSharpe / inSampleSharpe;
        var pbo = Math.Max(0, 1 - ratio);

        return Math.Min(1, pbo);  // Clamp to [0, 1]
    }

    private decimal CalculateDailySharePercentile(List<(DateOnly Date, decimal Return)> dailyReturns)
    {
        if (dailyReturns.Count == 0) return 0;

        var sharpe = CalculateSharpeRatio(dailyReturns);

        // Simplified: map Sharpe to percentile (empirical distribution)
        // Full: compare against historical model population
        if (sharpe < 0) return 0.05m;
        if (sharpe < 0.5m) return 0.30m;
        if (sharpe < 1.0m) return 0.60m;
        if (sharpe < 1.5m) return 0.80m;
        if (sharpe < 2.0m) return 0.95m;

        return 0.99m;
    }
}
