namespace KArtSell.Modules.ModelOperations.PortfolioReconciliation;

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Dapper;

/// <summary>
/// Data access layer for reconciliation using Dapper.
/// Implements IReconciliationRepository.
/// </summary>
public class ReconciliationSql : IReconciliationRepository
{
    private readonly IDbConnection _connection;

    public ReconciliationSql(IDbConnection connection)
    {
        _connection = connection ?? throw new ArgumentNullException(nameof(connection));
    }

    public Task<Holding?> GetHoldingAsync(Guid securityId) => GetHoldingAsync(securityId, transaction: null);

    /// <summary>
    /// DEBT-018: once a transaction is active on the shared connection (see
    /// ReconcileTradeHandler), every command on that connection must be given the transaction
    /// explicitly or Npgsql throws — this overload is required, not optional, when a caller has
    /// called BeginTransaction() on the same IDbConnection instance.
    /// </summary>
    public async Task<Holding?> GetHoldingAsync(Guid securityId, IDbTransaction? transaction)
    {
        const string sql = """
            SELECT id, security_id, quantity, weighted_avg_cost, total_cost_basis,
                   market_value, unrealized_gain_loss, updated_at, published_at,
                   correlation_id, revision
            FROM portfolio_management.holdings
            WHERE security_id = @security_id
            ORDER BY published_at DESC
            LIMIT 1
            """;

        return await _connection.QueryFirstOrDefaultAsync<Holding>(
            sql,
            new { security_id = securityId },
            transaction);
    }

    public Task UpsertHoldingAsync(Holding holding) => UpsertHoldingAsync(holding, transaction: null);

    /// <summary>
    /// DEBT-018: pass the transaction the caller is also using for the outbox write (see
    /// ReconcileTradeHandler) so the holding update and the outbox event commit atomically.
    /// </summary>
    public async Task UpsertHoldingAsync(Holding holding, IDbTransaction? transaction)
    {
        const string sql = """
            INSERT INTO portfolio_management.holdings
            (id, security_id, quantity, weighted_avg_cost, total_cost_basis, market_value,
             unrealized_gain_loss, updated_at, published_at, correlation_id, revision)
            VALUES (@id, @security_id, @quantity, @weighted_avg_cost, @total_cost_basis,
                    @market_value, @unrealized_gain_loss, @updated_at, @published_at,
                    @correlation_id, @revision)
            ON CONFLICT (id) DO UPDATE SET
              quantity = @quantity,
              weighted_avg_cost = @weighted_avg_cost,
              total_cost_basis = @total_cost_basis,
              market_value = @market_value,
              unrealized_gain_loss = @unrealized_gain_loss,
              updated_at = @updated_at,
              published_at = @published_at,
              revision = revision + 1
            """;

        await _connection.ExecuteAsync(sql, holding, transaction);
    }

    public Task InsertReconciliationLogAsync(ReconciliationLog log) => InsertReconciliationLogAsync(log, transaction: null);

    /// <summary>
    /// DEBT-018: pass the transaction the caller is also using for the outbox write (see
    /// ReconcileTradeHandler) so the log entry and the outbox event commit atomically.
    /// </summary>
    public async Task InsertReconciliationLogAsync(ReconciliationLog log, IDbTransaction? transaction)
    {
        const string sql = """
            INSERT INTO portfolio_management.reconciliation_logs
            (id, trade_id, holding_id, quantity_before, quantity_after,
             cost_basis_delta, unrealized_gain_loss_delta, mismatch_detected,
             mismatch_reason, reconciled_at, published_at, correlation_id)
            VALUES (@id, @trade_id, @holding_id, @quantity_before, @quantity_after,
                    @cost_basis_delta, @unrealized_gain_loss_delta, @mismatch_detected,
                    @mismatch_reason, @reconciled_at, @published_at, @correlation_id)
            """;

        await _connection.ExecuteAsync(sql, new
        {
            log.Id,
            log.TradeId,
            log.HoldingId,
            log.QuantityBefore,
            log.QuantityAfter,
            log.CostBasisDelta,
            log.UnrealizedGainLossDelta,
            log.MismatchDetected,
            log.MismatchReason,
            log.ReconciledAt,
            log.PublishedAt,
            log.CorrelationId
        }, transaction);
    }

    public async Task<List<ReconciliationLog>> GetReconciliationLogsAsync(
        DateTime startDate,
        DateTime endDate)
    {
        const string sql = """
            SELECT id, trade_id, holding_id, quantity_before, quantity_after,
                   cost_basis_delta, unrealized_gain_loss_delta, mismatch_detected,
                   mismatch_reason, reconciled_at, published_at, correlation_id
            FROM portfolio_management.reconciliation_logs
            WHERE published_at >= @start_date AND published_at <= @end_date
            ORDER BY published_at DESC
            """;

        var logs = await _connection.QueryAsync<ReconciliationLog>(sql, new
        {
            start_date = startDate,
            end_date = endDate
        });

        return logs.ToList();
    }

    /// <summary>
    /// Gets mismatch summary for reporting.
    /// </summary>
    public async Task<int> GetMismatchCountAsync(DateTime startDate, DateTime endDate)
    {
        const string sql = """
            SELECT COUNT(*)
            FROM portfolio_management.reconciliation_logs
            WHERE mismatch_detected = TRUE
              AND published_at >= @start_date
              AND published_at <= @end_date
            """;

        return await _connection.QueryFirstAsync<int>(sql, new
        {
            start_date = startDate,
            end_date = endDate
        });
    }

    /// <summary>
    /// Gets all open holdings (for portfolio view).
    /// </summary>
    public async Task<List<Holding>> GetOpenHoldingsAsync()
    {
        const string sql = """
            SELECT id, security_id, quantity, weighted_avg_cost, total_cost_basis,
                   market_value, unrealized_gain_loss, updated_at, published_at,
                   correlation_id, revision
            FROM portfolio_management.holdings
            WHERE quantity > 0
            ORDER BY published_at DESC
            """;

        var holdings = await _connection.QueryAsync<Holding>(sql);
        return holdings.ToList();
    }

    /// <summary>
    /// Gets mismatches by reason for analysis.
    /// </summary>
    public async Task<Dictionary<string, int>> GetMismatchesByReasonAsync(
        DateTime startDate,
        DateTime endDate)
    {
        const string sql = """
            SELECT mismatch_reason, COUNT(*) as count
            FROM portfolio_management.reconciliation_logs
            WHERE mismatch_detected = TRUE
              AND published_at >= @start_date
              AND published_at <= @end_date
            GROUP BY mismatch_reason
            ORDER BY count DESC
            """;

        var results = await _connection.QueryAsync<(string reason, int count)>(sql, new
        {
            start_date = startDate,
            end_date = endDate
        });

        return results.ToDictionary(r => r.reason ?? "Unknown", r => r.count);
    }
}
