namespace KArtSell.Modules.ModelOperations.Domain;

/// <summary>
/// VS-04 DOMAIN: Portfolio Composition Policy
///
/// Pure business logic (no I/O):
/// - Aggregate positions into portfolio
/// - Calculate weights
/// - Detect drift vs target
/// - Validate rebalance feasibility
///
/// All decisions: deterministic, testable, traceable
/// </summary>

public record Position(
    string Symbol,
    decimal Quantity,
    decimal MarketPrice,
    decimal CostBasisPerUnit);

public record PortfolioSnapshot(
    Guid PortfolioId,
    DateOnly SnapshotDate,
    List<Position> Positions,
    decimal TotalMarketValue);

public record TargetWeight(
    string Symbol,
    decimal TargetPercent);

public record WeightBreakdown(
    string Symbol,
    decimal Quantity,
    decimal MarketValue,
    decimal WeightPercent,
    decimal TargetPercent,
    decimal DriftPercent);

public record RebalanceAnalysis(
    List<WeightBreakdown> Breakdown,
    decimal WorstDriftPercent,
    bool ExceedsDriftThreshold,
    List<string> TradesRequired);

public static class PortfolioPolicy
{
    /// <summary>
    /// Aggregate positions into portfolio snapshot
    /// Calculates total market value
    /// </summary>
    public static PortfolioSnapshot AggregatePortfolio(
        Guid portfolioId,
        DateOnly snapshotDate,
        List<Position> positions)
    {
        if (positions == null || positions.Count == 0)
            return new PortfolioSnapshot(portfolioId, snapshotDate, new(), 0);

        var totalValue = positions
            .Where(p => p.Quantity > 0 && p.MarketPrice > 0)
            .Sum(p => p.Quantity * p.MarketPrice);

        return new PortfolioSnapshot(portfolioId, snapshotDate, positions, totalValue);
    }

    /// <summary>
    /// Calculate current weights from portfolio snapshot
    /// </summary>
    public static List<WeightBreakdown> CalculateCurrentWeights(PortfolioSnapshot portfolio)
    {
        if (portfolio.TotalMarketValue == 0)
            return new();

        return portfolio.Positions
            .Where(p => p.Quantity > 0 && p.MarketPrice > 0)
            .Select(p =>
            {
                var value = p.Quantity * p.MarketPrice;
                var weight = (value / portfolio.TotalMarketValue) * 100;
                return new WeightBreakdown(
                    Symbol: p.Symbol,
                    Quantity: p.Quantity,
                    MarketValue: value,
                    WeightPercent: Math.Round(weight, 2),
                    TargetPercent: 0,
                    DriftPercent: 0);
            })
            .OrderByDescending(w => w.WeightPercent)
            .ToList();
    }

    /// <summary>
    /// Detect drift from target weights
    /// </summary>
    public static RebalanceAnalysis AnalyzeDrift(
        PortfolioSnapshot portfolio,
        List<TargetWeight> targetWeights,
        decimal driftThreshold)
    {
        var currentWeights = CalculateCurrentWeights(portfolio);

        var breakdown = currentWeights
            .Select(current =>
            {
                var target = targetWeights.FirstOrDefault(t => t.Symbol == current.Symbol)?.TargetPercent ?? 0;
                var drift = Math.Abs(current.WeightPercent - target);
                return current with
                {
                    TargetPercent = target,
                    DriftPercent = Math.Round(drift, 2)
                };
            })
            .ToList();

        // Add missing symbols (not in current portfolio)
        foreach (var target in targetWeights.Where(t => !breakdown.Any(b => b.Symbol == t.Symbol)))
        {
            breakdown.Add(new WeightBreakdown(
                Symbol: target.Symbol,
                Quantity: 0,
                MarketValue: 0,
                WeightPercent: 0,
                TargetPercent: target.TargetPercent,
                DriftPercent: target.TargetPercent));
        }

        var worstDrift = breakdown.Max(b => b.DriftPercent);
        var exceedsDrift = worstDrift > driftThreshold;

        // Determine trades (rebalance to target)
        var trades = breakdown
            .Where(b => b.DriftPercent > driftThreshold / 2) // Trade if drift > half threshold
            .Select(b => b.WeightPercent > b.TargetPercent
                ? $"SELL {b.Symbol} to reduce {b.WeightPercent}% → {b.TargetPercent}%"
                : $"BUY {b.Symbol} to increase {b.WeightPercent}% → {b.TargetPercent}%")
            .ToList();

        return new RebalanceAnalysis(
            Breakdown: breakdown.OrderByDescending(b => b.DriftPercent).ToList(),
            WorstDriftPercent: worstDrift,
            ExceedsDriftThreshold: exceedsDrift,
            TradesRequired: trades);
    }

    /// <summary>
    /// Validate position concentrations (risk limits)
    /// </summary>
    public static (bool IsValid, List<string> Violations) ValidateConcentration(
        PortfolioSnapshot portfolio,
        decimal maxSinglePosition = 40,
        decimal maxTopFivePercent = 60)
    {
        var violations = new List<string>();
        var weights = CalculateCurrentWeights(portfolio);

        // Check single position limit
        var maxPosition = weights.FirstOrDefault();
        if (maxPosition != null && maxPosition.WeightPercent > maxSinglePosition)
            violations.Add($"Single position {maxPosition.Symbol} exceeds {maxSinglePosition}% limit (actual: {maxPosition.WeightPercent}%)");

        // Check top-5 concentration
        var topFive = weights.Take(5).Sum(w => w.WeightPercent);
        if (topFive > maxTopFivePercent)
            violations.Add($"Top 5 holdings exceed {maxTopFivePercent}% limit (actual: {topFive}%)");

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

    /// <summary>
    /// Calculate rebalance cost (trading slippage + fees)
    /// Rough estimate: 0.1% per trade, 0.05% per share
    /// </summary>
    public static decimal EstimateRebalanceCost(
        RebalanceAnalysis analysis,
        decimal slippageBps = 10m,      // 10 basis points per trade
        decimal feePercent = 0.001m)    // 0.1% commission
    {
        var tradeCount = analysis.TradesRequired.Count;
        var portfolioValue = analysis.Breakdown.Sum(b => b.MarketValue);

        if (portfolioValue == 0)
            return 0;

        var slippageCost = (portfolioValue * slippageBps / 10000);
        var tradeFeesCost = (portfolioValue * feePercent) * tradeCount;

        return Math.Round(slippageCost + tradeFeesCost, 2);
    }

    /// <summary>
    /// Detect if portfolio is sufficiently balanced (no rebalance needed)
    /// </summary>
    public static bool IsBalanced(
        RebalanceAnalysis analysis,
        decimal driftThreshold = 5)
    {
        return analysis.WorstDriftPercent <= driftThreshold;
    }

    /// <summary>
    /// Validate rebalance request (feasibility check)
    /// </summary>
    public static (bool IsValid, List<string> Issues) ValidateRebalanceRequest(
        PortfolioSnapshot portfolio,
        List<TargetWeight> targetWeights,
        decimal minPortfolioValue = 1000)
    {
        var issues = new List<string>();

        if (portfolio.TotalMarketValue < minPortfolioValue)
            issues.Add($"Portfolio too small (${portfolio.TotalMarketValue}, minimum ${minPortfolioValue})");

        if (!targetWeights.Any())
            issues.Add("No target weights specified");

        var targetSum = targetWeights.Sum(t => t.TargetPercent);
        if (Math.Abs(targetSum - 100) > 1m) // Allow 1% tolerance
            issues.Add($"Target weights don't sum to 100% (actual: {targetSum}%)");

        foreach (var target in targetWeights.Where(t => t.TargetPercent < 0 || t.TargetPercent > 100))
            issues.Add($"Invalid target weight for {target.Symbol}: {target.TargetPercent}%");

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

    /// <summary>
    /// Generate rebalance summary (human-readable)
    /// </summary>
    public static string SummarizeRebalance(RebalanceAnalysis analysis)
    {
        if (analysis.TradesRequired.Count == 0)
            return "Portfolio is already balanced. No trades needed.";

        var summary = $"Rebalancing required ({analysis.TradesRequired.Count} trades):\n";
        foreach (var trade in analysis.TradesRequired.Take(5))
        {
            summary += $"  • {trade}\n";
        }

        if (analysis.TradesRequired.Count > 5)
            summary += $"  • ... and {analysis.TradesRequired.Count - 5} more trades";

        return summary;
    }
}
