namespace KArtSell.Modules.ModelOperations.PortfolioReconciliation;

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using KArtSell.BuildingBlocks.Time;

/// <summary>
/// Orchestrates portfolio reconciliation after trade execution.
/// Coordinates cost basis calculation, mismatch detection, and logging.
/// </summary>
public class ReconciliationEngine
{
    private readonly CostBasisCalculator _costBasisCalc;
    private readonly MismatchDetector _mismatchDetector;
    private readonly IReconciliationRepository _repository;
    private readonly IClock _clock;

    public ReconciliationEngine(
        CostBasisCalculator costBasisCalc,
        MismatchDetector mismatchDetector,
        IReconciliationRepository repository,
        IClock clock)
    {
        _costBasisCalc = costBasisCalc ?? throw new ArgumentNullException(nameof(costBasisCalc));
        _mismatchDetector = mismatchDetector ?? throw new ArgumentNullException(nameof(mismatchDetector));
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _clock = clock ?? throw new ArgumentNullException(nameof(clock));
    }

    /// <summary>
    /// Reconciles a trade and updates holdings.
    /// </summary>
    public async Task<ReconciliationResult> ReconcileTradeAsync(
        Guid tradeId,
        Guid securityId,
        int executedQuantity,
        decimal executedPrice,
        int approvedQuantity,
        decimal approvedPrice,
        DateTime tradeDate,
        DateTime expectedSettlementDate,
        DateTime? actualSettlementDate,
        Guid correlationId,
        System.Data.IDbTransaction? transaction = null)
    {
        var result = new ReconciliationResult { CorrelationId = correlationId };
        var now = _clock.UtcNow.UtcDateTime;

        try
        {
            // Load current holding. DEBT-018: must use the transaction-aware overload once a
            // transaction is active on the shared connection (see ReconcileTradeHandler), or
            // Npgsql throws on this read.
            var holding = await _repository.GetHoldingAsync(securityId, transaction);
            if (holding == null)
            {
                holding = new Holding
                {
                    Id = Guid.NewGuid(),
                    SecurityId = securityId,
                    Quantity = 0,
                    WeightedAvgCost = 0m,
                    TotalCostBasis = 0m,
                    CorrelationId = correlationId
                };
            }

            var quantityBefore = holding.Quantity;
            var costBasisBefore = holding.TotalCostBasis;

            // Determine if buy or sell
            var isBuy = executedQuantity > 0;

            if (isBuy)
            {
                // Update holdings for buy
                var newWeightedAvgCost = _costBasisCalc.CalculateWeightedAverageCost(
                    holding.Quantity,
                    holding.TotalCostBasis,
                    executedQuantity,
                    executedPrice);

                holding.Quantity += executedQuantity;
                holding.WeightedAvgCost = newWeightedAvgCost;
                holding.TotalCostBasis = holding.Quantity * newWeightedAvgCost;
            }
            else
            {
                // Update holdings for sell
                var sellQuantity = Math.Abs(executedQuantity);
                holding.Quantity -= sellQuantity;
                holding.TotalCostBasis = holding.Quantity > 0
                    ? holding.Quantity * holding.WeightedAvgCost
                    : 0m;
            }

            holding.UpdatedAt = now;
            holding.PublishedAt = now;

            // Detect mismatches
            var mismatches = _mismatchDetector.DetectMismatches(
                approvedQuantity,
                executedQuantity,
                approvedPrice,
                executedPrice,
                tradeDate,
                expectedSettlementDate,
                actualSettlementDate,
                costBasisBefore,
                holding.TotalCostBasis,
                now);

            var mismatchDetected = mismatches.Count > 0;

            // Save holding. DEBT-018: when a transaction is supplied, the write commits
            // atomically with the outbox event ReconcileTradeHandler publishes afterward.
            await _repository.UpsertHoldingAsync(holding, transaction);

            // Log reconciliation
            var logEntry = new ReconciliationLog
            {
                Id = Guid.NewGuid(),
                TradeId = tradeId,
                HoldingId = holding.Id,
                QuantityBefore = quantityBefore,
                QuantityAfter = holding.Quantity,
                CostBasisDelta = holding.TotalCostBasis - costBasisBefore,
                UnrealizedGainLossDelta = 0m, // TODO: calculate if market value available
                MismatchDetected = mismatchDetected,
                MismatchReason = mismatchDetected ? FormatMismatchReason(mismatches) : null,
                ReconciledAt = now,
                PublishedAt = now,
                CorrelationId = correlationId
            };

            await _repository.InsertReconciliationLogAsync(logEntry, transaction);

            result.Success = true;
            result.Holding = holding;
            result.Mismatches = mismatches;
            result.MismatchDetected = mismatchDetected;
        }
        catch (Exception ex)
        {
            result.Success = false;
            result.Error = ex.Message;
        }

        return result;
    }

    /// <summary>
    /// Generates daily reconciliation report.
    /// </summary>
    public async Task<ReconciliationReport> GenerateDailyReportAsync(
        DateTime reportDate,
        Guid correlationId)
    {
        var logs = await _repository.GetReconciliationLogsAsync(
            reportDate.Date,
            reportDate.Date.AddDays(1).AddSeconds(-1));

        var report = new ReconciliationReport
        {
            ReportDate = reportDate,
            TotalLogsProcessed = logs.Count,
            CorrelationId = correlationId,
            GeneratedAt = _clock.UtcNow.UtcDateTime
        };

        var highSeverityCount = 0;
        var mediumSeverityCount = 0;

        foreach (var log in logs)
        {
            if (log.MismatchDetected)
            {
                // Simple severity counting (could be enhanced)
                if (log.MismatchReason?.Contains("HIGH") == true)
                    highSeverityCount++;
                else if (log.MismatchReason?.Contains("MEDIUM") == true)
                    mediumSeverityCount++;
            }
        }

        report.MismatchesByHighSeverity = highSeverityCount;
        report.MismatchesByMediumSeverity = mediumSeverityCount;
        report.TotalMismatches = highSeverityCount + mediumSeverityCount;
        report.MismatchPercentage = report.TotalLogsProcessed > 0
            ? (double)report.TotalMismatches / report.TotalLogsProcessed * 100
            : 0;

        return report;
    }

    private string FormatMismatchReason(List<Mismatch> mismatches)
    {
        return string.Join("; ", mismatches.ConvertAll(m => $"{m.Type}({m.Severity})"));
    }
}

public class ReconciliationResult
{
    public bool Success { get; set; }
    public Holding? Holding { get; set; }
    public List<Mismatch> Mismatches { get; set; } = new();
    public bool MismatchDetected { get; set; }
    public string? Error { get; set; }
    public Guid CorrelationId { get; set; }
}

public class ReconciliationReport
{
    public DateTime ReportDate { get; set; }
    public int TotalLogsProcessed { get; set; }
    public int TotalMismatches { get; set; }
    public int MismatchesByHighSeverity { get; set; }
    public int MismatchesByMediumSeverity { get; set; }
    public double MismatchPercentage { get; set; }
    public DateTime GeneratedAt { get; set; }
    public Guid CorrelationId { get; set; }
}

public class Holding
{
    public Guid Id { get; set; }
    public Guid SecurityId { get; set; }
    public int Quantity { get; set; }
    public decimal WeightedAvgCost { get; set; }
    public decimal TotalCostBasis { get; set; }
    public decimal? MarketValue { get; set; }
    public decimal? UnrealizedGainLoss { get; set; }
    public DateTime UpdatedAt { get; set; }
    public DateTime PublishedAt { get; set; }
    public Guid CorrelationId { get; set; }
    public int Revision { get; set; } = 1;
}

public class ReconciliationLog
{
    public Guid Id { get; set; }
    public Guid TradeId { get; set; }
    public Guid HoldingId { get; set; }
    public int QuantityBefore { get; set; }
    public int QuantityAfter { get; set; }
    public decimal CostBasisDelta { get; set; }
    public decimal UnrealizedGainLossDelta { get; set; }
    public bool MismatchDetected { get; set; }
    public string? MismatchReason { get; set; }
    public DateTime ReconciledAt { get; set; }
    public DateTime PublishedAt { get; set; }
    public Guid CorrelationId { get; set; }
}

/// <summary>
/// Repository interface for reconciliation persistence.
/// </summary>
public interface IReconciliationRepository
{
    Task<Holding?> GetHoldingAsync(Guid securityId);
    Task<Holding?> GetHoldingAsync(Guid securityId, System.Data.IDbTransaction? transaction);
    Task UpsertHoldingAsync(Holding holding);
    Task UpsertHoldingAsync(Holding holding, System.Data.IDbTransaction? transaction);
    Task InsertReconciliationLogAsync(ReconciliationLog log);
    Task InsertReconciliationLogAsync(ReconciliationLog log, System.Data.IDbTransaction? transaction);
    Task<List<ReconciliationLog>> GetReconciliationLogsAsync(DateTime startDate, DateTime endDate);
    Task<List<Holding>> GetOpenHoldingsAsync();
}
