namespace KArtSell.Modules.ModelOperations.Domain;

/// <summary>
/// VS-06 DOMAIN: Stress Testing Policy
///
/// Pure business logic (no I/O):
/// - Apply scenario shocks to prices
/// - Calculate portfolio loss under stress
/// - Identify worst-case exposures
///
/// All scenarios: deterministic, repeatable
/// </summary>

public record ScenarioShock(
    string AssetClass,
    decimal PriceShockPercent,
    decimal VolatilityMultiplier = 1.0m);

public record StressedPosition(
    string Symbol,
    decimal BaselinePrice,
    decimal StressedPrice,
    decimal Quantity,
    decimal BaselineValue,
    decimal StressedValue,
    decimal Loss,
    decimal LossPercent);

public record StressScenarioResult(
    string ScenarioId,
    decimal BaselinePortfolioValue,
    decimal StressedPortfolioValue,
    decimal PortfolioLoss,
    decimal PortfolioLossPercent,
    List<StressedPosition> PositionResults,
    StressedPosition WorstPosition,
    decimal BaselineVAR,
    decimal StressedVAR);

public static class StressTestingPolicy
{
    /// <summary>
    /// Apply price shocks to positions (scenario)
    /// </summary>
    public static List<StressedPosition> ApplyScenarioShock(
        List<WeightBreakdown> currentPositions,
        List<ScenarioShock> shocks,
        Func<string, string> getAssetClass) // Map symbol to asset class
    {
        var results = new List<StressedPosition>();

        foreach (var position in currentPositions.Where(p => p.MarketValue > 0))
        {
            var assetClass = getAssetClass(position.Symbol);
            var shock = shocks.FirstOrDefault(s => s.AssetClass == assetClass)
                ?? shocks.First(); // Default shock if not found

            // Apply price shock
            var shockFactor = 1 + shock.PriceShockPercent;
            var baselinePrice = position.MarketValue / position.Quantity;
            var stressedPrice = baselinePrice * shockFactor;

            var stressedValue = position.Quantity * stressedPrice;
            var loss = stressedValue - position.MarketValue;
            var lossPercent = (loss / position.MarketValue) * 100;

            results.Add(new StressedPosition(
                Symbol: position.Symbol,
                BaselinePrice: Math.Round(baselinePrice, 2),
                StressedPrice: Math.Round(stressedPrice, 2),
                Quantity: position.Quantity,
                BaselineValue: position.MarketValue,
                StressedValue: Math.Round(stressedValue, 2),
                Loss: Math.Round(loss, 2),
                LossPercent: Math.Round(lossPercent, 2)));
        }

        return results.OrderBy(p => p.Loss).ToList(); // Worst first
    }

    /// <summary>
    /// Calculate portfolio-level impact
    /// </summary>
    public static StressScenarioResult CalculateStressResult(
        string scenarioId,
        decimal baselinePortfolioValue,
        decimal baselineVAR,
        List<StressedPosition> stressedPositions)
    {
        if (!stressedPositions.Any())
        {
            var emptyResult = new StressedPosition(
                Symbol: "",
                BaselinePrice: 0,
                StressedPrice: 0,
                Quantity: 0,
                BaselineValue: 0,
                StressedValue: 0,
                Loss: 0,
                LossPercent: 0);
            return new StressScenarioResult(
                scenarioId, baselinePortfolioValue, baselinePortfolioValue, 0, 0,
                new(), emptyResult, baselineVAR, baselineVAR);
        }

        var stressedPortfolioValue = stressedPositions.Sum(p => p.StressedValue);
        var totalLoss = stressedPortfolioValue - baselinePortfolioValue;
        var lossPercent = (totalLoss / baselinePortfolioValue) * 100;

        var worstPosition = stressedPositions.FirstOrDefault() ?? stressedPositions.First(); // Already sorted

        // Estimate VAR increase (rough: loss increases VAR proportionally)
        var varChange = Math.Abs(lossPercent) / 100 * baselineVAR;
        var stressedVAR = baselineVAR + varChange;

        return new StressScenarioResult(
            ScenarioId: scenarioId,
            BaselinePortfolioValue: baselinePortfolioValue,
            StressedPortfolioValue: Math.Round(stressedPortfolioValue, 2),
            PortfolioLoss: Math.Round(totalLoss, 2),
            PortfolioLossPercent: Math.Round(lossPercent, 2),
            PositionResults: stressedPositions,
            WorstPosition: worstPosition,
            BaselineVAR: baselineVAR,
            StressedVAR: Math.Round(stressedVAR, 2));
    }

    /// <summary>
    /// Predefined scenarios (library)
    /// </summary>
    public static List<ScenarioShock> GetBullScenario()
    {
        return new()
        {
            new("Equities", 0.15m, 0.8m),
            new("Bonds", 0, 0.7m),
            new("Alternatives", 0.10m, 0.9m),
        };
    }

    public static List<ScenarioShock> GetBearScenario()
    {
        return new()
        {
            new("Equities", -0.20m, 1.5m),
            new("Bonds", 0.015m, 1.2m),
            new("Alternatives", -0.15m, 1.3m),
        };
    }

    public static List<ScenarioShock> GetRateShockScenario()
    {
        return new()
        {
            new("Equities", -0.08m, 1.1m),
            new("Bonds", 0.020m, 1.0m), // +200 bps
            new("Alternatives", -0.05m, 0.95m),
        };
    }

    public static List<ScenarioShock> GetVolSpikeScenario()
    {
        return new()
        {
            new("Equities", -0.10m, 5.0m),
            new("Bonds", 0.005m, 2.0m),
            new("Alternatives", -0.08m, 3.0m),
        };
    }

    /// <summary>
    /// Determine scenario severity (user-facing label)
    /// </summary>
    public static string ClassifySeverity(decimal lossPercent)
    {
        return Math.Abs(lossPercent) switch
        {
            < 5 => "Mild",
            < 10 => "Moderate",
            < 20 => "Severe",
            _ => "Extreme"
        };
    }

    /// <summary>
    /// Identify concentration-driven losses
    /// If top-5 losses account for >70% of total, concentration is a factor
    /// </summary>
    public static bool IsConcentrationDriven(List<StressedPosition> positions)
    {
        if (positions.Count == 0)
            return false;

        var totalAbsLoss = positions.Sum(p => Math.Abs(p.Loss));
        if (totalAbsLoss == 0)
            return false;

        var top5Loss = positions.Take(5).Sum(p => Math.Abs(p.Loss));
        var concentrationRatio = top5Loss / totalAbsLoss;

        return concentrationRatio > 0.70m;
    }

    /// <summary>
    /// Validate scenario definition (sanity checks)
    /// </summary>
    public static (bool IsValid, List<string> Issues) ValidateScenario(List<ScenarioShock> shocks)
    {
        var issues = new List<string>();

        if (!shocks.Any())
            issues.Add("Scenario must have at least one shock");

        foreach (var shock in shocks.Where(s => s.PriceShockPercent < -1 || s.PriceShockPercent > 1))
            issues.Add($"Extreme price shock: {shock.AssetClass} {shock.PriceShockPercent:P}");

        foreach (var shock in shocks.Where(s => s.VolatilityMultiplier <= 0 || s.VolatilityMultiplier > 10))
            issues.Add($"Invalid volatility multiplier: {shock.AssetClass} {shock.VolatilityMultiplier}x");

        return (issues.Count == 0, issues);
    }

    /// <summary>
    /// Generate scenario summary (human-readable)
    /// </summary>
    public static string SummarizeStressResult(StressScenarioResult result)
    {
        var summary = $"Scenario: {result.ScenarioId}\n";
        summary += $"Portfolio Loss: ${result.PortfolioLoss:N2} ({result.PortfolioLossPercent:N2}%)\n";

        if (result.WorstPosition != null)
        {
            summary += $"Worst Position: {result.WorstPosition.Symbol} loses ${Math.Abs(result.WorstPosition.Loss):N2}\n";
        }

        summary += $"Stress VAR Change: ${result.StressedVAR - result.BaselineVAR:N2}";

        return summary;
    }
}
