namespace KArtSell.Modules.ModelOperations.Domain;

/// <summary>
/// VS-05 DOMAIN: Risk Metrics Policy
///
/// Pure business logic (no I/O):
/// - Value at Risk (VAR) calculation
/// - Sharpe ratio (risk-adjusted return)
/// - Sortino ratio (downside focus)
/// - Concentration metrics
///
/// All calculations: deterministic, numerically stable
/// </summary>

public record PriceHistory(
    string Symbol,
    List<(DateOnly Date, decimal Price)> Prices);

public record PortfolioReturns(
    List<decimal> DailyReturns,
    int SampleSize);

public record RiskMetrics(
    decimal VAR95,
    decimal Sharpe,
    decimal Sortino,
    decimal Volatility,
    decimal TopFivePercent,
    decimal HirschmanIndex,
    decimal MaxSinglePosition);

public static class RiskMetricsPolicy
{
    /// <summary>
    /// Calculate daily returns from price series
    /// </summary>
    public static PortfolioReturns CalculateReturns(
        List<decimal> prices,
        int lookbackDays = 252)
    {
        if (prices.Count < 2)
            return new PortfolioReturns(new(), 0);

        var returns = new List<decimal>();
        for (int i = 1; i < prices.Count && i <= lookbackDays; i++)
        {
            if (prices[i - 1] > 0)
            {
                var dailyReturn = (prices[i] - prices[i - 1]) / prices[i - 1];
                returns.Add(dailyReturn);
            }
        }

        return new PortfolioReturns(returns, returns.Count);
    }

    /// <summary>
    /// Calculate Value at Risk (95% confidence, parametric method)
    /// VAR = Mean - (1.645 * StdDev)
    /// </summary>
    public static decimal CalculateVAR95(
        PortfolioReturns returns,
        decimal portfolioValue)
    {
        if (returns.DailyReturns.Count < 30)
            return 0; // Insufficient data

        var mean = returns.DailyReturns.Average();
        var variance = returns.DailyReturns.Sum(r => (r - mean) * (r - mean)) / returns.DailyReturns.Count;
        var stdDev = (decimal)Math.Sqrt((double)variance);

        // 95% confidence: z-score = 1.645
        var dailyVAR = mean - (1.645m * stdDev);

        // Annualize (252 trading days)
        var annualizedVAR = dailyVAR * (decimal)Math.Sqrt(252);

        // Apply to portfolio value
        return Math.Abs(annualizedVAR * portfolioValue);
    }

    /// <summary>
    /// Calculate Sharpe Ratio
    /// Sharpe = (Return - RiskFreeRate) / StdDev
    /// </summary>
    public static decimal CalculateSharpe(
        PortfolioReturns returns,
        decimal riskFreeRate = 0.045m)
    {
        if (returns.DailyReturns.Count < 30)
            return 0;

        var mean = returns.DailyReturns.Average();
        var variance = returns.DailyReturns.Sum(r => (r - mean) * (r - mean)) / returns.DailyReturns.Count;
        var stdDev = (decimal)Math.Sqrt((double)variance);

        if (stdDev == 0)
            return 0;

        // Annualize
        var annualReturn = (1 + mean) * (decimal)Math.Pow(1 + (double)mean, 251) - 1;
        var annualVolatility = stdDev * (decimal)Math.Sqrt(252);

        return Math.Round((annualReturn - riskFreeRate) / annualVolatility, 3);
    }

    /// <summary>
    /// Calculate Sortino Ratio (downside focus)
    /// Sortino = (Return - RiskFreeRate) / DownsideDeviation
    /// </summary>
    public static decimal CalculateSortino(
        PortfolioReturns returns,
        decimal riskFreeRate = 0.045m)
    {
        if (returns.DailyReturns.Count < 30)
            return 0;

        var mean = returns.DailyReturns.Average();

        // Downside deviation (only negative returns)
        var downsideVariance = returns.DailyReturns
            .Where(r => r < 0)
            .Sum(r => r * r) / returns.DailyReturns.Count;
        var downsideDeviation = (decimal)Math.Sqrt((double)downsideVariance);

        if (downsideDeviation == 0)
            return 0;

        // Annualize
        var annualReturn = (1 + mean) * (decimal)Math.Pow(1 + (double)mean, 251) - 1;
        var annualDownsideDeviation = downsideDeviation * (decimal)Math.Sqrt(252);

        return Math.Round((annualReturn - riskFreeRate) / annualDownsideDeviation, 3);
    }

    /// <summary>
    /// Calculate annualized volatility
    /// </summary>
    public static decimal CalculateVolatility(PortfolioReturns returns)
    {
        if (returns.DailyReturns.Count < 30)
            return 0;

        var mean = returns.DailyReturns.Average();
        var variance = returns.DailyReturns.Sum(r => (r - mean) * (r - mean)) / returns.DailyReturns.Count;
        var dailyStdDev = (decimal)Math.Sqrt((double)variance);

        return Math.Round(dailyStdDev * (decimal)Math.Sqrt(252), 4);
    }

    /// <summary>
    /// Calculate concentration metrics
    /// Top-5 as %, Hirschman index (0-1)
    /// </summary>
    public static (decimal TopFivePercent, decimal HirschmanIndex, decimal MaxPosition) CalculateConcentration(
        List<WeightBreakdown> weights)
    {
        if (!weights.Any())
            return (0, 0, 0);

        var topFive = weights.Take(5).Sum(w => w.WeightPercent);
        var maxPosition = weights.First().WeightPercent; // Already sorted descending

        // Hirschman Index (Herfindahl): Σ(weight%)²
        var hirschman = weights.Sum(w => w.WeightPercent * w.WeightPercent) / 10000m;

        return (
            Math.Round(topFive, 2),
            Math.Round(Math.Min(hirschman, 1), 2),
            Math.Round(maxPosition, 2));
    }

    /// <summary>
    /// Detect concentration risks
    /// </summary>
    public static List<string> DetectConcentrationRisks(
        List<WeightBreakdown> weights,
        decimal maxSinglePosition = 40,
        decimal maxTopFivePercent = 60)
    {
        var risks = new List<string>();

        if (!weights.Any())
            return risks;

        var maxPosition = weights.First().WeightPercent;
        if (maxPosition > maxSinglePosition)
            risks.Add($"High single-position concentration: {maxPosition}% > {maxSinglePosition}%");

        var topFive = weights.Take(5).Sum(w => w.WeightPercent);
        if (topFive > maxTopFivePercent)
            risks.Add($"High top-5 concentration: {topFive}% > {maxTopFivePercent}%");

        return risks;
    }

    /// <summary>
    /// Calculate data quality score
    /// Factors: price availability, return distribution, sample size
    /// </summary>
    public static (int QualityScore, List<string> Issues) AssessDataQuality(
        PortfolioReturns returns,
        int minSampleSize = 30)
    {
        var score = 100;
        var issues = new List<string>();

        if (returns.SampleSize < minSampleSize)
        {
            score -= (minSampleSize - returns.SampleSize) * 2;
            issues.Add($"Insufficient data: {returns.SampleSize} days < {minSampleSize}");
        }

        // Check for extreme values
        if (returns.DailyReturns.Any(r => r > 1 || r < -1))
        {
            score -= 30;
            issues.Add("Extreme or invalid returns detected");
        }

        // Check distribution skewness (simplified)
        var mean = returns.DailyReturns.Average();
        var outliers = returns.DailyReturns.Count(r => Math.Abs(r - mean) > 0.1m);
        if (outliers > returns.SampleSize * 0.1m)
        {
            score -= 15;
            issues.Add("High outlier count detected");
        }

        return (Math.Max(0, score), issues);
    }
}
