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

namespace KArtSell.Host.Features.Portfolio;

#region ========== VS-06: STRESS TESTING ==========

public sealed class TriggerStressTestRequest
{
    public string ScenarioId { get; set; } = "bear";
}

public sealed class StressTestResponse
{
    public Guid StressTestId { get; set; }
    public string Status { get; set; } = "Queued";
    public string ScenarioId { get; set; } = "";
    public string CorrelationId { get; set; } = "";
    public DateTime QueuedAt { get; set; }
}

public sealed class GetStressResultResponse
{
    public Guid StressTestId { get; set; }
    public string ScenarioId { get; set; } = "";
    public decimal PortfolioLoss { get; set; }
    public decimal PortfolioLossPercent { get; set; }
    public decimal BaselineVAR { get; set; }
    public decimal StressedVAR { get; set; }
    public DateTime CompletedAt { get; set; }
}

public sealed class TriggerStressTestEndpoint : Endpoint<TriggerStressTestRequest, StressTestResponse>
{
    private readonly IStressTestService _stressService;
    private readonly IClock _clock;

    public TriggerStressTestEndpoint(IStressTestService stressService, IClock clock)
    {
        _stressService = stressService;
        _clock = clock;
    }

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

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

        var correlationId = HttpContext.TraceIdentifier;
        var stressTestId = await _stressService.ScheduleStressTestAsync(portfolioId, req.ScenarioId, correlationId, ct);

        HttpContext.Response.StatusCode = StatusCodes.Status202Accepted;
        HttpContext.Response.ContentType = "application/json";
        await HttpContext.Response.WriteAsync(JsonSerializer.Serialize(new StressTestResponse
        {
            StressTestId = stressTestId,
            Status = "Queued",
            ScenarioId = req.ScenarioId,
            CorrelationId = correlationId,
            QueuedAt = _clock.UtcNow.DateTime,
        }), ct);
    }
}

public interface IStressTestService
{
    Task<Guid> ScheduleStressTestAsync(Guid portfolioId, string scenarioId, string correlationId, CancellationToken ct);
}

public class StressTestService : IStressTestService
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IBackgroundJobClient _jobClient;

    public StressTestService(NpgsqlDataSource dataSource, IBackgroundJobClient jobClient)
    {
        _dataSource = dataSource;
        _jobClient = jobClient;
    }

    public async Task<Guid> ScheduleStressTestAsync(Guid portfolioId, string scenarioId, string correlationId, CancellationToken ct)
    {
        var testId = Guid.NewGuid();

        const string sql = """
            INSERT INTO risk_management.stress_test_results
                (stress_test_id, portfolio_id, scenario_id, run_date, correlation_id, status)
            VALUES (@testId, @portfolioId, @scenarioId, CURRENT_DATE, @correlationId, 'Queued');
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(ct);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("@testId", testId);
        cmd.Parameters.AddWithValue("@portfolioId", portfolioId);
        cmd.Parameters.AddWithValue("@scenarioId", scenarioId);
        cmd.Parameters.AddWithValue("@correlationId", correlationId);

        await cmd.ExecuteNonQueryAsync(ct);

        _jobClient.Enqueue<IStressTestJob>(j =>
            j.ExecuteAsync(testId, portfolioId, scenarioId, correlationId, CancellationToken.None));

        return testId;
    }
}

public interface IStressTestJob
{
    Task ExecuteAsync(Guid stressTestId, Guid portfolioId, string scenarioId, string correlationId, CancellationToken ct);
}

public class StressTestJobHandler : IStressTestJob
{
    private readonly NpgsqlDataSource _dataSource;

    public StressTestJobHandler(NpgsqlDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task ExecuteAsync(Guid stressTestId, Guid portfolioId, string scenarioId, string correlationId, CancellationToken ct)
    {
        try
        {
            await Task.Delay(2000, ct); // Mock processing

            // Update results (mock: -20% loss for bear scenario)
            var loss = scenarioId == "bear" ? -20.0m : 0m;

            const string sql = """
                UPDATE risk_management.stress_test_results
                SET status = 'Completed', portfolio_loss_percent = @loss, completed_at = CURRENT_TIMESTAMP
                WHERE stress_test_id = @testId;
                """;

            await using var connection = await _dataSource.OpenConnectionAsync(ct);
            await using var cmd = connection.CreateCommand();
            cmd.CommandText = sql;
            cmd.Parameters.AddWithValue("@testId", stressTestId);
            cmd.Parameters.AddWithValue("@loss", loss);

            await cmd.ExecuteNonQueryAsync(ct);
        }
        catch (Exception)
        {
            // Publish event on completion
            const string updateSql = """
                UPDATE risk_management.stress_test_results
                SET status = 'Failed' WHERE stress_test_id = @testId;
                """;

            await using var connection = await _dataSource.OpenConnectionAsync(ct);
            await using var cmd = connection.CreateCommand();
            cmd.CommandText = updateSql;
            cmd.Parameters.AddWithValue("@testId", stressTestId);
            await cmd.ExecuteNonQueryAsync(ct);
            throw;
        }
    }
}

#endregion

#region ========== VS-07: RISK ALERTS ==========

public sealed class GetAlertsResponse
{
    public List<AlertDto> ActiveAlerts { get; set; } = new();
    public List<AlertDto> ResolvedAlerts { get; set; } = new();
}

public sealed class AlertDto
{
    public Guid AlertId { get; set; }
    public string ThresholdType { get; set; } = "";
    public string Severity { get; set; } = "";
    public decimal CurrentValue { get; set; }
    public decimal Threshold { get; set; }
    public DateTime TriggeredAt { get; set; }
    public string Message { get; set; } = "";
}

public sealed class GetAlertsEndpoint : EndpointWithoutRequest<GetAlertsResponse>
{
    private readonly IAlertService _alertService;

    public GetAlertsEndpoint(IAlertService alertService)
    {
        _alertService = alertService;
    }

    public override void Configure()
    {
        Get("/api/portfolio/{portfolioId}/alerts");
        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 alerts = await _alertService.GetAlertsAsync(portfolioId, ct);

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

public interface IAlertService
{
    Task<GetAlertsResponse> GetAlertsAsync(Guid portfolioId, CancellationToken ct);
}

public class AlertService : IAlertService
{
    private readonly NpgsqlDataSource _dataSource;

    public AlertService(NpgsqlDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task<GetAlertsResponse> GetAlertsAsync(Guid portfolioId, CancellationToken ct)
    {
        const string activeSql = """
            SELECT alert_id, threshold_type, status, current_value, threshold_value, triggered_at, message
            FROM risk_management.risk_alerts
            WHERE portfolio_id = @portfolioId AND removed_at IS NULL AND status IN ('Initial', 'Warning', 'Critical')
            ORDER BY critical_at DESC NULLS LAST;
            """;

        const string resolvedSql = """
            SELECT alert_id, threshold_type, status, current_value, threshold_value, triggered_at, message
            FROM risk_management.risk_alerts
            WHERE portfolio_id = @portfolioId AND removed_at IS NOT NULL AND status = 'Resolved'
            ORDER BY resolved_at DESC LIMIT 10;
            """;

        var response = new GetAlertsResponse();

        await using var connection = await _dataSource.OpenConnectionAsync(ct);

        // Fetch active alerts
        await using var cmd1 = connection.CreateCommand();
        cmd1.CommandText = activeSql;
        cmd1.Parameters.AddWithValue("@portfolioId", portfolioId);

        await using var reader1 = await cmd1.ExecuteReaderAsync(ct);
        while (await reader1.ReadAsync(ct))
        {
            response.ActiveAlerts.Add(new AlertDto
            {
                AlertId = reader1.GetGuid(0),
                ThresholdType = reader1.GetString(1),
                Severity = reader1.GetString(2),
                CurrentValue = reader1.GetDecimal(3),
                Threshold = reader1.GetDecimal(4),
                TriggeredAt = reader1.GetDateTime(5),
                Message = reader1.GetString(6),
            });
        }

        // Fetch resolved alerts
        await using var cmd2 = connection.CreateCommand();
        cmd2.CommandText = resolvedSql;
        cmd2.Parameters.AddWithValue("@portfolioId", portfolioId);

        await using var reader2 = await cmd2.ExecuteReaderAsync(ct);
        while (await reader2.ReadAsync(ct))
        {
            response.ResolvedAlerts.Add(new AlertDto
            {
                AlertId = reader2.GetGuid(0),
                ThresholdType = reader2.GetString(1),
                Severity = reader2.GetString(2),
                CurrentValue = reader2.GetDecimal(3),
                Threshold = reader2.GetDecimal(4),
                TriggeredAt = reader2.GetDateTime(5),
                Message = reader2.GetString(6),
            });
        }

        return response;
    }
}

public interface IAlertEscalationJob
{
    Task ExecuteAsync(CancellationToken ct);
}

public class AlertEscalationJobHandler : IAlertEscalationJob
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly IClock _clock;

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

    public async Task ExecuteAsync(CancellationToken ct)
    {
        // Scheduled every 1 minute (after risk metrics update)
        // Evaluate all active alerts for escalation/resolution

        const string sql = """
            SELECT alert_id, threshold_type, status, triggered_at
            FROM risk_management.risk_alerts
            WHERE removed_at IS NULL AND status IN ('Initial', 'Warning');
            """;

        await using var connection = await _dataSource.OpenConnectionAsync(ct);
        await using var cmd = connection.CreateCommand();
        cmd.CommandText = sql;

        await using var reader = await cmd.ExecuteReaderAsync(ct);
        while (await reader.ReadAsync(ct))
        {
            var alertId = reader.GetGuid(0);
            var status = reader.GetString(2);
            var triggeredAt = reader.GetDateTime(3);

            var minutesElapsed = (int)(_clock.UtcNow.DateTime - triggeredAt).TotalMinutes;

            // Simple escalation: warn at 2 min, critical at 5 min
            if (status == "Initial" && minutesElapsed >= 2)
            {
                const string updateSql = "UPDATE risk_management.risk_alerts SET status = 'Warning', warned_at = CURRENT_TIMESTAMP WHERE alert_id = @alertId;";
                await using var updateCmd = connection.CreateCommand();
                updateCmd.CommandText = updateSql;
                updateCmd.Parameters.AddWithValue("@alertId", alertId);
                await updateCmd.ExecuteNonQueryAsync(ct);
            }
            else if (status == "Warning" && minutesElapsed >= 5)
            {
                const string updateSql = "UPDATE risk_management.risk_alerts SET status = 'Critical', critical_at = CURRENT_TIMESTAMP WHERE alert_id = @alertId;";
                await using var updateCmd = connection.CreateCommand();
                updateCmd.CommandText = updateSql;
                updateCmd.Parameters.AddWithValue("@alertId", alertId);
                await updateCmd.ExecuteNonQueryAsync(ct);
            }
        }
    }
}

#endregion
