using System;
using System.Collections.Generic;
using System.Linq;

namespace QuantEngine.Core.Domain;

public record BacktestTrade(
    string Ticker,
    DateTime EntryDate,
    DateTime ExitDate,
    decimal EntryPrice,
    decimal ExitPrice,
    int Quantity,
    decimal ReturnRate,
    decimal FeeCost
);

public record BacktestResult(
    string RunId,
    decimal SharpeRatio,
    decimal MaxDrawdown,
    decimal AnnualizedReturn,
    decimal TurnoverRate,
    decimal CostDrag,
    string GateStatus
);

/// <summary>
/// Point-in-time Backtesting & Transaction Cost Model Engine
/// SOLID: Single Responsibility for deterministic quantitative backtesting
/// </summary>
public class Backtester
{
    private const decimal DefaultFeeRateBps = 15m; // 15 bps per trade
    private const decimal SlippageBps = 5m;       // 5 bps slippage

    public BacktestResult RunBacktest(
        string runId,
        List<decimal> dailyPortfolioValues,
        List<BacktestTrade> trades,
        decimal initialCapital)
    {
        if (dailyPortfolioValues == null || dailyPortfolioValues.Count < 2)
        {
            return new BacktestResult(runId, 0m, 0m, 0m, 0m, 0m, "FAIL_INSUFFICIENT_DATA");
        }

        // 1. Daily Returns & Sharpe Ratio Calculation
        var dailyReturns = new List<decimal>();
        for (int i = 1; i < dailyPortfolioValues.Count; i++)
        {
            var prev = dailyPortfolioValues[i - 1];
            var curr = dailyPortfolioValues[i];
            var ret = prev > 0 ? (curr - prev) / prev : 0m;
            dailyReturns.Add(ret);
        }

        var avgReturn = dailyReturns.Average();
        var stdDev = CalculateStdDev(dailyReturns);
        var annualFactor = (decimal)Math.Sqrt(252);
        var sharpeRatio = stdDev > 0 ? (avgReturn / stdDev) * annualFactor : 0m;

        // 2. Max Drawdown (MDD) Calculation
        decimal peak = dailyPortfolioValues[0];
        decimal maxDrawdown = 0m;
        foreach (var val in dailyPortfolioValues)
        {
            if (val > peak) peak = val;
            var dd = peak > 0 ? (peak - val) / peak : 0m;
            if (dd > maxDrawdown) maxDrawdown = dd;
        }

        // 3. Turnover Rate & Cost Drag
        decimal totalTradedVolume = trades.Sum(t => (t.EntryPrice * t.Quantity) + (t.ExitPrice * t.Quantity));
        decimal totalFees = trades.Sum(t => t.FeeCost) + (totalTradedVolume * (FeeRateBpsToRatio(DefaultFeeRateBps + SlippageBps)));
        
        decimal turnoverRate = initialCapital > 0 ? totalTradedVolume / initialCapital : 0m;
        decimal costDrag = initialCapital > 0 ? totalFees / initialCapital : 0m;

        decimal totalReturn = (dailyPortfolioValues.Last() - dailyPortfolioValues[0]) / dailyPortfolioValues[0];
        decimal annualizedReturn = totalReturnsToAnnualized(totalReturn, dailyPortfolioValues.Count);

        return new BacktestResult(
            runId,
            Math.Round(sharpeRatio, 4),
            Math.Round(maxDrawdown, 4),
            Math.Round(annualizedReturn, 4),
            Math.Round(turnoverRate, 4),
            Math.Round(costDrag, 4),
            "PASS"
        );
    }

    private static decimal CalculateStdDev(List<decimal> values)
    {
        if (values.Count < 2) return 0m;
        var avg = values.Average();
        var sumSquares = values.Sum(v => (v - avg) * (v - avg));
        var variance = sumSquares / (values.Count - 1);
        return (decimal)Math.Sqrt((double)variance);
    }

    private static decimal FeeRateBpsToRatio(decimal bps) => bps / 10000m;

    private static decimal totalReturnsToAnnualized(decimal totalReturn, int days)
    {
        if (days <= 0) return 0m;
        double years = days / 252.0;
        if (years <= 0) return totalReturn;
        double compound = Math.Pow((double)(1m + totalReturn), 1.0 / years) - 1.0;
        return (decimal)compound;
    }
}
