using System.Data.Common;
using System.Text.Json;
using Dapper;
using Microsoft.Extensions.Logging;
using Npgsql;

namespace KArtSell.Modules.ModelOperations.TradeExecution;

public interface ITradeSql
{
    Task<Trade?> GetTradeByIdAsync(Guid tradeId, Guid correlationId, CancellationToken ct = default);
    Task<Trade?> GetTradeByKisOrderIdAsync(string kisOrderId, Guid correlationId, CancellationToken ct = default);
    Task<IEnumerable<Trade>> GetTradesByStatusAsync(TradeStatus status, Guid correlationId, CancellationToken ct = default);
    Task<IEnumerable<Trade>> GetTradesByDecisionIdAsync(Guid sellDecisionId, Guid correlationId, CancellationToken ct = default);
    Task InsertTradeAsync(Trade trade, CancellationToken ct = default);
    Task UpdateTradeStatusAsync(Trade trade, JsonElement? kisResponse, string? errorMessage, CancellationToken ct = default);

    /// <summary>
    /// DEBT-018: transaction-aware overload so the caller can write the outbox event on the same
    /// connection/transaction as this status update, committing both atomically. See
    /// TradeOutboxPublisher/SubmitTradeHandler.
    /// </summary>
    Task UpdateTradeStatusAsync(DbConnection connection, DbTransaction transaction, Trade trade, JsonElement? kisResponse, string? errorMessage, CancellationToken ct = default);
    Task<int> CountTradesByStatusAsync(TradeStatus status, CancellationToken ct = default);
}

public class TradeSql : ITradeSql
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly ILogger<TradeSql> _logger;

    public TradeSql(NpgsqlDataSource dataSource, ILogger<TradeSql> logger)
    {
        _dataSource = dataSource;
        _logger = logger;
    }

    public async Task<Trade?> GetTradeByIdAsync(Guid tradeId, Guid correlationId, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity,
                   unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response,
                   execution_timestamp, settlement_timestamp, published_at, correlation_id, revision
            FROM model_operations.trades
            WHERE id = @tradeId
              AND published_at <= NOW()
            ORDER BY published_at DESC, revision DESC
            LIMIT 1
            """;

        var trade = await connection.QueryFirstOrDefaultAsync<Trade>(
            sql,
            new { tradeId }
        );

        if (trade != null)
        {
            _logger.LogInformation("Retrieved trade {TradeId}", tradeId);
        }

        return trade;
    }

    public async Task<Trade?> GetTradeByKisOrderIdAsync(string kisOrderId, Guid correlationId, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity,
                   unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response,
                   execution_timestamp, settlement_timestamp, published_at, correlation_id, revision
            FROM model_operations.trades
            WHERE kis_order_id = @kisOrderId
              AND published_at <= NOW()
            ORDER BY published_at DESC, revision DESC
            LIMIT 1
            """;

        return await connection.QueryFirstOrDefaultAsync<Trade>(
            sql,
            new { kisOrderId }
        );
    }

    public async Task<IEnumerable<Trade>> GetTradesByStatusAsync(TradeStatus status, Guid correlationId, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity,
                   unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response,
                   execution_timestamp, settlement_timestamp, published_at, correlation_id, revision
            FROM model_operations.trades
            WHERE status = @status
              AND published_at <= NOW()
            ORDER BY published_at DESC
            """;

        return await connection.QueryAsync<Trade>(
            sql,
            new { status = status.ToString() }
        );
    }

    public async Task<IEnumerable<Trade>> GetTradesByDecisionIdAsync(Guid sellDecisionId, Guid correlationId, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            SELECT id, sell_decision_id, kis_order_id, status, quantity, executed_quantity,
                   unit_price, total_amount, commission, net_proceeds, error_message, kis_response::text as kis_response,
                   execution_timestamp, settlement_timestamp, published_at, correlation_id, revision
            FROM model_operations.trades
            WHERE sell_decision_id = @sellDecisionId
              AND published_at <= NOW()
            ORDER BY published_at DESC
            """;

        return await connection.QueryAsync<Trade>(
            sql,
            new { sellDecisionId }
        );
    }

    public async Task InsertTradeAsync(Trade trade, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            INSERT INTO model_operations.trades
            (id, sell_decision_id, kis_order_id, status, quantity, executed_quantity,
             unit_price, total_amount, commission, net_proceeds, error_message, kis_response,
             execution_timestamp, settlement_timestamp, published_at, correlation_id, revision)
            VALUES (@id, @sellDecisionId, @kisOrderId, @status, @quantity, @executedQuantity,
                    @unitPrice, @totalAmount, @commission, @netProceeds, @errorMessage, @kisResponse::jsonb,
                    @executionTimestamp, @settlementTimestamp, @publishedAt, @correlationId, @revision)
            """;

        await connection.ExecuteAsync(sql, new
        {
            trade.Id,
            trade.SellDecisionId,
            trade.KisOrderId,
            status = trade.Status.ToString(),
            trade.Quantity,
            trade.ExecutedQuantity,
            trade.UnitPrice,
            trade.TotalAmount,
            trade.Commission,
            trade.NetProceeds,
            trade.ErrorMessage,
            trade.KisResponse,
            trade.ExecutionTimestamp,
            trade.SettlementTimestamp,
            trade.PublishedAt,
            trade.CorrelationId,
            trade.Revision
        });

        _logger.LogInformation("Inserted trade {TradeId}", trade.Id);
    }

    private const string UpdateTradeStatusSql = """
        INSERT INTO model_operations.trade_status_history
        (id, trade_id, old_status, new_status, transitioned_at, kis_response, error_message, published_at, correlation_id)
        SELECT @id, id, status, @newStatus, NOW(), @kisResponse::jsonb, @errorMessage, NOW(), @correlationId
        FROM model_operations.trades
        WHERE id = @tradeId;

        UPDATE model_operations.trades
        SET status = @newStatus,
            kis_order_id = COALESCE(@kisOrderId, kis_order_id),
            executed_quantity = COALESCE(@executedQuantity, executed_quantity),
            unit_price = COALESCE(@unitPrice, unit_price),
            total_amount = COALESCE(@totalAmount, total_amount),
            commission = COALESCE(@commission, commission),
            net_proceeds = COALESCE(@netProceeds, net_proceeds),
            execution_timestamp = COALESCE(@executionTimestamp, execution_timestamp),
            settlement_timestamp = COALESCE(@settlementTimestamp, settlement_timestamp),
            kis_response = COALESCE(@kisResponse::jsonb, kis_response),
            error_message = COALESCE(@errorMessage, error_message),
            revision = revision + 1
        WHERE id = @tradeId
        """;

    private static object BuildUpdateTradeStatusParams(Trade trade, JsonElement? kisResponse, string? errorMessage) => new
    {
        id = Guid.NewGuid(),
        tradeId = trade.Id,
        newStatus = trade.Status.ToString(),
        kisOrderId = trade.KisOrderId,
        executedQuantity = trade.ExecutedQuantity,
        unitPrice = trade.UnitPrice,
        totalAmount = trade.TotalAmount,
        commission = trade.Commission,
        netProceeds = trade.NetProceeds,
        executionTimestamp = trade.ExecutionTimestamp,
        settlementTimestamp = trade.SettlementTimestamp,
        kisResponse = kisResponse?.ToString(),
        errorMessage,
        correlationId = trade.CorrelationId
    };

    public async Task UpdateTradeStatusAsync(
        Trade trade,
        JsonElement? kisResponse,
        string? errorMessage,
        CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);
        await connection.ExecuteAsync(UpdateTradeStatusSql, BuildUpdateTradeStatusParams(trade, kisResponse, errorMessage));

        _logger.LogInformation("Updated trade {TradeId} status to {Status}", trade.Id, trade.Status);
    }

    public async Task UpdateTradeStatusAsync(
        DbConnection connection,
        DbTransaction transaction,
        Trade trade,
        JsonElement? kisResponse,
        string? errorMessage,
        CancellationToken ct = default)
    {
        await connection.ExecuteAsync(new CommandDefinition(
            UpdateTradeStatusSql,
            BuildUpdateTradeStatusParams(trade, kisResponse, errorMessage),
            transaction,
            cancellationToken: ct));

        _logger.LogInformation("Updated trade {TradeId} status to {Status} (transactional)", trade.Id, trade.Status);
    }

    public async Task<int> CountTradesByStatusAsync(TradeStatus status, CancellationToken ct = default)
    {
        using var connection = await _dataSource.OpenConnectionAsync(ct);

        const string sql = """
            SELECT COUNT(*)
            FROM model_operations.trades
            WHERE status = @status
              AND published_at <= NOW()
            """;

        return await connection.QueryFirstAsync<int>(
            sql,
            new { status = status.ToString() }
        );
    }
}
