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-05 BE: Risk Metrics Endpoint
/// GET /api/portfolio/{id}/risk - Fetch current risk metrics
///
/// Returns: VAR, Sharpe, Sortino, volatility, concentration
/// Scheduled: Daily at 9:30 KST (after market open)
/// Cached: < 1 hour
/// </summary>

public sealed class RiskMetricsResponse
{
    public Guid PortfolioId { get; set; }
    public DateOnly CalculationDate { get; set; }
    public RiskMetricsDto Metrics { get; set; } = new();
    public int QualityScore { get; set; }
    public DateTime LastUpdate { get; set; }
}

public sealed class RiskMetricsDto
{
    public decimal VAR95Amount { get; set; }
    public decimal VAR95Percent { get; set; }
    public decimal SharpeRatio { get; set; }
    public decimal SortinoRatio { get; set; }
    public decimal Volatility { get; set; }
    public decimal TopFivePercent { get; set; }
    public decimal HirschmanIndex { get; set; }
    public decimal MaxSinglePosition { get; set; }
}

public sealed class GetRiskMetricsEndpoint : EndpointWithoutRequest<RiskMetricsResponse>
{
    private readonly IRiskMetricsService _metricsService;
    private readonly IClock _clock;

    public GetRiskMetricsEndpoint(IRiskMetricsService metricsService, IClock clock)
    {
        _metricsService = metricsService;
        _clock = clock;
    }

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

    public override async Task HandleAsync(CancellationToken ct)
    {
        var portfolioIdStr = Route<string>("portfolioId");
        if (!Guid.TryParse(portfolioIdStr, out var portfolioId))
        {
            ThrowError("Invalid portfolio ID");
            return;
        }

        var metrics = await _metricsService.GetMetricsAsync(portfolioId, ct);

        if (metrics == null)
        {
            ThrowError("Metrics not found or not yet calculated");
            return;
        }

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

/// <summary>
/// VS-05 Application Handler: Orchestrates risk calculation
/// </summary>

public interface IRiskMetricsService
{
    Task<RiskMetricsResponse?> GetMetricsAsync(Guid portfolioId, CancellationToken cancellationToken);
}

public class RiskMetricsService : IRiskMetricsService
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;

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

    public async Task<RiskMetricsResponse?> GetMetricsAsync(Guid portfolioId, CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT
                portfolio_id, calculation_date,
                var_95_amount, var_95_percent,
                sharpe_ratio, sortino_ratio, volatility_annualized,
                top_five_percent, hirschman_index, max_single_position,
                quality_score, published_at
            FROM risk_management.risk_metrics
            WHERE portfolio_id = @portfolioId
              AND published_at <= @cutoff
              AND removed_at IS NULL
            ORDER BY calculation_date 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 null;

        return new RiskMetricsResponse
        {
            PortfolioId = reader.GetGuid(0),
            CalculationDate = DateOnly.FromDateTime(reader.GetDateTime(1)),
            Metrics = new RiskMetricsDto
            {
                VAR95Amount = reader.GetDecimal(2),
                VAR95Percent = reader.GetDecimal(3),
                SharpeRatio = reader.GetDecimal(4),
                SortinoRatio = reader.GetDecimal(5),
                Volatility = reader.GetDecimal(6),
                TopFivePercent = reader.GetDecimal(7),
                HirschmanIndex = reader.GetDecimal(8),
                MaxSinglePosition = reader.GetDecimal(9),
            },
            QualityScore = reader.GetInt32(10),
            LastUpdate = reader.GetDateTime(11),
        };
    }
}

/// <summary>
/// VS-05 ASYNC: Daily Risk Calculation Job (Hangfire)
/// Scheduled: 9:30 KST (after market open, uses prices from 9:00)
/// </summary>

public interface IRiskCalculationJob
{
    Task ExecuteAsync(Guid portfolioId, DateOnly calculationDate, CancellationToken ct);
}

public class RiskCalculationJobHandler : IRiskCalculationJob
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;

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

    public async Task ExecuteAsync(Guid portfolioId, DateOnly calculationDate, CancellationToken ct)
    {
        var startTime = _clock.UtcNow;

        try
        {
            await UpdateJobStatusAsync(portfolioId, calculationDate, "Running", ct);

            // Fetch historical prices
            var priceHistory = await FetchPriceHistoryAsync(portfolioId, calculationDate, ct);

            if (priceHistory.Count == 0)
            {
                await UpdateJobStatusAsync(portfolioId, calculationDate, "Completed", ct);
                return;
            }

            // Calculate returns
            var returns = RiskMetricsPolicy.CalculateReturns(priceHistory, 252);

            // Calculate metrics
            var var95 = RiskMetricsPolicy.CalculateVAR95(returns, 100000m); // Mock: 100k portfolio
            var sharpe = RiskMetricsPolicy.CalculateSharpe(returns);
            var sortino = RiskMetricsPolicy.CalculateSortino(returns);
            var volatility = RiskMetricsPolicy.CalculateVolatility(returns);

            // Mock weights (real: fetch from VS-04)
            var weights = new List<WeightBreakdown>();
            var (topFive, hirschman, maxPosition) = RiskMetricsPolicy.CalculateConcentration(weights);

            var (qualityScore, _) = RiskMetricsPolicy.AssessDataQuality(returns);

            // Insert metrics
            await InsertMetricsAsync(portfolioId, calculationDate, var95, sharpe, sortino, volatility, topFive, hirschman, maxPosition, qualityScore, ct);

            var duration = (int)(_clock.UtcNow - startTime).TotalSeconds;
            await UpdateJobStatusAsync(portfolioId, calculationDate, "Completed", ct, duration);

            // Publish event
            await PublishMetricsEventAsync(portfolioId, calculationDate, ct);
        }
        catch (Exception ex)
        {
            await UpdateJobStatusAsync(portfolioId, calculationDate, "Failed", ct, null, ex.Message);
            throw;
        }
    }

    private async Task<List<decimal>> FetchPriceHistoryAsync(Guid portfolioId, DateOnly upToDate, CancellationToken ct)
    {
        // Mock: return empty list (real implementation: fetch from market_data schema)
        await Task.CompletedTask;
        return new();
    }

    private async Task UpdateJobStatusAsync(Guid portfolioId, DateOnly calculationDate, string status, CancellationToken ct, int? durationSeconds = null, string? errorMessage = null)
    {
        const string sql = """
            UPDATE risk_management.risk_calculation_jobs
            SET status = @status, completed_at = CASE WHEN @status IN ('Completed', 'Failed') THEN CURRENT_TIMESTAMP ELSE NULL END,
                duration_seconds = @duration, error_message = @error, updated_at = CURRENT_TIMESTAMP
            WHERE portfolio_id = @portfolioId AND calculation_date = @date;
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(ct);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@date", calculationDate);
        cmd.Parameters.AddWithValue("@status", status);
        cmd.Parameters.AddWithValue("@duration", durationSeconds ?? (object)DBNull.Value);
        cmd.Parameters.AddWithValue("@error", errorMessage ?? (object)DBNull.Value);

        await cmd.ExecuteNonQueryAsync(ct);
    }

    private async Task InsertMetricsAsync(
        Guid portfolioId, DateOnly calculationDate,
        decimal var95, decimal sharpe, decimal sortino, decimal volatility,
        decimal topFive, decimal hirschman, decimal maxPosition,
        int qualityScore, CancellationToken ct)
    {
        const string sql = """
            INSERT INTO risk_management.risk_metrics
                (portfolio_id, calculation_date, var_95_amount, var_95_percent, sharpe_ratio, sortino_ratio,
                 volatility_annualized, top_five_percent, hirschman_index, max_single_position, quality_score, published_at)
            VALUES (@portfolioId, @date, @var95, @var95Pct, @sharpe, @sortino, @vol, @top5, @hirsch, @maxPos, @quality, CURRENT_TIMESTAMP);
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(ct);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@date", calculationDate.ToDateTime(TimeOnly.MinValue));
        cmd.Parameters.AddWithValue("@var95", var95);
        cmd.Parameters.AddWithValue("@var95Pct", (var95 / 100000) * 100); // Mock percent
        cmd.Parameters.AddWithValue("@sharpe", sharpe);
        cmd.Parameters.AddWithValue("@sortino", sortino);
        cmd.Parameters.AddWithValue("@vol", volatility);
        cmd.Parameters.AddWithValue("@top5", topFive);
        cmd.Parameters.AddWithValue("@hirsch", hirschman);
        cmd.Parameters.AddWithValue("@maxPos", maxPosition);
        cmd.Parameters.AddWithValue("@quality", qualityScore);

        await cmd.ExecuteNonQueryAsync(ct);
    }

    private async Task PublishMetricsEventAsync(Guid portfolioId, DateOnly calculationDate, CancellationToken ct)
    {
        const string sql = """
            INSERT INTO shared.outbox (aggregate_id, event_type, payload, published_at, correlation_id)
            VALUES (@aggregateId, 'PortfolioMetricsCalculated', @payload, CURRENT_TIMESTAMP, @correlationId);
            """;

        var payload = JsonSerializer.Serialize(new
        {
            eventType = "PortfolioMetricsCalculated",
            portfolioId,
            calculationDate,
            calculatedAt = _clock.UtcNow.DateTime,
        });

        await using var connection = await _dataSource.OpenConnectionAsync(ct);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@aggregateId", portfolioId);
        cmd.Parameters.AddWithValue("@payload", payload);
        cmd.Parameters.AddWithValue("@correlationId", Guid.NewGuid().ToString());

        await cmd.ExecuteNonQueryAsync(ct);
    }
}
