using FastEndpoints;
using Hangfire;
using Npgsql;
using System.Text.Json;
using KArtSell.BuildingBlocks.Time;
using KArtSell.Modules.ModelOperations.Domain;

namespace KArtSell.Host.Features.Portfolio;

/// <summary>
/// VS-08 BE: Risk Dashboard Endpoint
/// GET /api/dashboard/risk - Fetch aggregated risk dashboard
///
/// Reads from VS-04~07 and combines into single response
/// Cached <1hr for performance; refreshed on event
/// </summary>

public sealed class DashboardResponse
{
    public Guid PortfolioId { get; set; }
    public DateOnly SnapshotDate { get; set; }
    public PortfolioDto Portfolio { get; set; } = new();
    public RiskMetricsDto08 RiskMetrics { get; set; } = new(0, 0, 0, 0, 0, 0);
    public List<StressResultDto08> StressResults { get; set; } = new();
    public List<AlertDto08> ActiveAlerts { get; set; } = new();
    public int HealthScore { get; set; }
    public List<string> RiskInsights { get; set; } = new();
    public DateTime LastUpdate { get; set; }
}

public sealed class PortfolioDto
{
    public decimal TotalValue { get; set; }
    public List<PositionSummaryDto> Positions { get; set; } = new();
}

public sealed class PositionSummaryDto
{
    public string Symbol { get; set; } = "";
    public decimal Quantity { get; set; }
    public decimal MarketPrice { get; set; }
    public decimal MarketValue { get; set; }
    public decimal WeightPercent { get; set; }
}

// Note: RiskMetricsDto and AlertDto already defined in VS-04/05 endpoints
// VS-08 reuses existing DTOs

// Using SimpleStressResult from policy for aggregation
public record StressAggregateData(
    string Scenario,
    decimal PortfolioLossPercent,
    decimal StressedVAR);

public record StressResultDto08(
    string Scenario,
    decimal PortfolioLossPercent,
    decimal StressedVAR);

public record RiskMetricsDto08(
    decimal VAR95,
    decimal SharpeRatio,
    decimal SortinoRatio,
    decimal VolatilityPercent,
    decimal TopFivePercent,
    decimal MaxPositionPercent);

public record AlertDto08(
    Guid AlertId,
    string Threshold,
    decimal CurrentValue,
    string Severity,
    string Message);

public sealed class GetRiskDashboardEndpoint : EndpointWithoutRequest<DashboardResponse>
{
    private readonly IDashboardService _dashboardService;
    private readonly IClock _clock;

    public GetRiskDashboardEndpoint(IDashboardService dashboardService, IClock clock)
    {
        _dashboardService = dashboardService;
        _clock = clock;
    }

    public override void Configure()
    {
        Get("/api/dashboard/risk");
        Roles("RiskAnalyst");
    }

    public override async Task HandleAsync(CancellationToken ct)
    {
        var portfolioIdStr = HttpContext.Request.Query["portfolioId"].ToString();
        if (!Guid.TryParse(portfolioIdStr, out var portfolioId))
        {
            ThrowError("Portfolio ID required");
            return;
        }

        var dashboard = await _dashboardService.GetDashboardAsync(portfolioId, ct);

        if (dashboard == null)
        {
            ThrowError("Portfolio not found");
            return;
        }

        HttpContext.Response.StatusCode = StatusCodes.Status200OK;
        HttpContext.Response.ContentType = "application/json";
        await HttpContext.Response.WriteAsync(JsonSerializer.Serialize(dashboard), ct);
    }
}

/// <summary>
/// VS-08 Application Handler: Aggregates VS-04~07 data
/// </summary>

public interface IDashboardService
{
    Task<DashboardResponse?> GetDashboardAsync(Guid portfolioId, CancellationToken cancellationToken);
}

public class DashboardService : IDashboardService
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;
    private static readonly Dictionary<Guid, (DateTime CachedAt, DashboardResponse Data)> _cache = new();
    private static readonly TimeSpan CacheTTL = TimeSpan.FromHours(1);

    public DashboardService(NpgsqlDataSource dataSource, IClock clock)
    {
        _dataSource = dataSource;
        _clock = clock;
    }

    public async Task<DashboardResponse?> GetDashboardAsync(Guid portfolioId, CancellationToken cancellationToken)
    {
        // Check cache
        if (_cache.TryGetValue(portfolioId, out var cached))
        {
            if (_clock.UtcNow - cached.CachedAt < CacheTTL)
                return cached.Data;

            _cache.Remove(portfolioId);
        }

        // Read from DB (VS-04~07 source tables)
        var portfolio = await FetchPortfolioAsync(portfolioId, cancellationToken);
        if (portfolio == null)
            return null;

        var riskMetrics = await FetchRiskMetricsAsync(portfolioId, cancellationToken);
        var stressDataList = await FetchStressResultsAsync(portfolioId, cancellationToken);
        var alerts = await FetchAlertsAsync(portfolioId, cancellationToken);

        var stressResults = stressDataList.Select(s => new SimpleStressResult(s.Scenario, s.PortfolioLossPercent, s.StressedVAR)).ToList();

        // Aggregate using policy (portfolio is guaranteed not null by earlier check)
        var portfolioPositions = portfolio!.Value.Item2.Select(p => new PortfolioPosition(
            p.Symbol, p.Quantity, p.MarketPrice, p.MarketValue, 0)).ToList();

        var aggregatedPortfolio = DashboardPolicy.AggregatePortfolio(portfolioPositions);

        var riskMetricsSnapshot = new RiskMetricsSnapshot(
            riskMetrics.VAR95,
            riskMetrics.SharpeRatio,
            riskMetrics.SortinoRatio,
            riskMetrics.VolatilityPercent,
            riskMetrics.TopFivePercent,
            riskMetrics.MaxPositionPercent);

        var riskInsights = DashboardPolicy.SummarizeRiskInsights(riskMetricsSnapshot, stressResults, alerts);
        var healthScore = DashboardPolicy.CalculateHealthScore(riskMetricsSnapshot, alerts);

        var response = new DashboardResponse
        {
            PortfolioId = portfolioId,
            SnapshotDate = DateOnly.FromDateTime(_clock.UtcNow.DateTime),
            Portfolio = new PortfolioDto
            {
                TotalValue = aggregatedPortfolio.TotalValue,
                Positions = aggregatedPortfolio.Positions.Select(p => new PositionSummaryDto
                {
                    Symbol = p.Symbol,
                    Quantity = p.Quantity,
                    MarketPrice = p.MarketPrice,
                    MarketValue = p.MarketValue,
                    WeightPercent = p.WeightPercent,
                }).ToList(),
            },
            RiskMetrics = new RiskMetricsDto08(
                riskMetrics.VAR95,
                riskMetrics.SharpeRatio,
                riskMetrics.SortinoRatio,
                riskMetrics.VolatilityPercent,
                riskMetrics.TopFivePercent,
                riskMetrics.MaxPositionPercent),
            StressResults = stressResults.Select(s => new StressResultDto08(
                s.Scenario,
                s.PortfolioLossPercent,
                s.StressedVAR)).ToList(),
            ActiveAlerts = alerts.Select(a => new AlertDto08(
                a.AlertId,
                a.Threshold,
                a.CurrentValue,
                a.Severity,
                a.Message)).ToList(),
            HealthScore = healthScore,
            RiskInsights = riskInsights,
            LastUpdate = _clock.UtcNow.UtcDateTime,
        };

        // Cache result
        _cache[portfolioId] = (_clock.UtcNow.UtcDateTime, response);

        return response;
    }

    private async Task<(decimal TotalValue, List<(string Symbol, decimal Quantity, decimal MarketPrice, decimal MarketValue)>)?> FetchPortfolioAsync(
        Guid portfolioId,
        CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT symbol, quantity, market_price, market_value
            FROM risk_management.portfolio_positions
            WHERE portfolio_id = @portfolioId
              AND published_at <= @cutoff
              AND removed_at IS NULL
              AND trading_date = CURRENT_DATE
            ORDER BY market_value DESC;
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@cutoff", _clock.UtcNow);

        var positions = new List<(string, decimal, decimal, decimal)>();
        decimal totalValue = 0;

        await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            var marketValue = reader.GetDecimal(3);
            positions.Add((reader.GetString(0), reader.GetDecimal(1), reader.GetDecimal(2), marketValue));
            totalValue += marketValue;
        }

        return positions.Count > 0 ? (totalValue, positions) : null;
    }

    private async Task<RiskMetricsSnapshot> FetchRiskMetricsAsync(Guid portfolioId, CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT var95, sharpe_ratio, sortino_ratio, volatility_percent,
                   concentration_top_five_percent, max_position_percent
            FROM risk_management.risk_metrics
            WHERE portfolio_id = @portfolioId
              AND published_at <= @cutoff
              AND removed_at IS NULL
            ORDER BY published_at DESC
            LIMIT 1;
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@cutoff", _clock.UtcNow);

        await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
        if (await reader.ReadAsync(cancellationToken))
        {
            return new RiskMetricsSnapshot(
                reader.GetDecimal(0),
                reader.GetDecimal(1),
                reader.GetDecimal(2),
                reader.GetDecimal(3),
                reader.GetDecimal(4),
                reader.GetDecimal(5));
        }

        return new RiskMetricsSnapshot(0, 0, 0, 0, 0, 0);
    }

    private async Task<List<StressAggregateData>> FetchStressResultsAsync(Guid portfolioId, CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT scenario_name, portfolio_loss_percent, stressed_var
            FROM risk_management.stress_test_results
            WHERE portfolio_id = @portfolioId
              AND published_at <= @cutoff
              AND removed_at IS NULL
            ORDER BY published_at DESC;
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@cutoff", _clock.UtcNow);

        var results = new List<StressAggregateData>();
        await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            results.Add(new StressAggregateData(
                reader.GetString(0),
                reader.GetDecimal(1),
                reader.GetDecimal(2)));
        }

        return results;
    }

    private async Task<List<ActiveAlert>> FetchAlertsAsync(Guid portfolioId, CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT alert_id, threshold_type, current_value, severity, message
            FROM risk_management.risk_alerts
            WHERE portfolio_id = @portfolioId
              AND published_at <= @cutoff
              AND removed_at IS NULL
              AND resolved_at IS NULL
            ORDER BY severity DESC, triggered_at DESC;
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(cancellationToken);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@cutoff", _clock.UtcNow);

        var alerts = new List<ActiveAlert>();
        await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            alerts.Add(new ActiveAlert(
                reader.GetGuid(0),
                reader.GetString(1),
                reader.GetDecimal(2),
                reader.GetString(3),
                reader.GetString(4)));
        }

        return alerts;
    }
}

/// <summary>
/// VS-08 ASYNC: Dashboard Update Listener
/// Refreshes cache on events from VS-04~07
/// </summary>

public interface IDashboardUpdateJob
{
    Task ExecuteAsync(Guid portfolioId, string changedComponent, CancellationToken ct);
}

public class DashboardUpdateJobHandler : IDashboardUpdateJob
{
    private readonly IDashboardService _dashboardService;
    private readonly IClock _clock;

    public DashboardUpdateJobHandler(IDashboardService dashboardService, IClock clock)
    {
        _dashboardService = dashboardService;
        _clock = clock;
    }

    public async Task ExecuteAsync(Guid portfolioId, string changedComponent, CancellationToken ct)
    {
        // Refresh dashboard cache by calling GetDashboardAsync
        // This forces cache invalidation and reload
        await _dashboardService.GetDashboardAsync(portfolioId, ct);

        // Publish SignalR event (would be done via DashboardHub in real implementation)
        // For now, just log that update occurred
        Console.WriteLine($"Dashboard cache refreshed for portfolio {portfolioId} due to {changedComponent}");
    }
}
