namespace KArtSell.Integration.Tests.PortfolioReconciliation;

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using KArtSell.Modules.ModelOperations.PortfolioReconciliation;
using Xunit;

public class ReconciliationEngineTests
{
    private readonly CostBasisCalculator _costCalc;
    private readonly MismatchDetector _mismatchDetector;

    public ReconciliationEngineTests()
    {
        _costCalc = new CostBasisCalculator();
        _mismatchDetector = new MismatchDetector();
    }

    [Fact]
    public void CalculateWeightedAverageCost_BuyFirst_Success()
    {
        // Arrange
        int previousQuantity = 0;
        decimal previousCostBasis = 0m;
        int buyQuantity = 100;
        decimal buyPrice = 150m;

        // Act
        var result = _costCalc.CalculateWeightedAverageCost(
            previousQuantity, previousCostBasis, buyQuantity, buyPrice);

        // Assert
        Assert.Equal(150m, result);
    }

    [Fact]
    public void CalculateWeightedAverageCost_SecondBuy_Success()
    {
        // Arrange
        int previousQuantity = 100;
        decimal previousCostBasis = 15000m; // 100 * 150
        int buyQuantity = 50;
        decimal buyPrice = 160m;

        // Act
        var result = _costCalc.CalculateWeightedAverageCost(
            previousQuantity, previousCostBasis, buyQuantity, buyPrice);

        // Assert
        var expected = (15000m + (50 * 160m)) / 150m; // (15000 + 8000) / 150 = 153.33
        Assert.Equal(expected, result, 2);
    }

    [Fact]
    public void CalculateRealizedGainLoss_Profit_Success()
    {
        // Arrange
        int sellQuantity = 100;
        decimal sellPrice = 160m;
        decimal weightedAverageCost = 150m;

        // Act
        var result = _costCalc.CalculateRealizedGainLoss(
            sellQuantity, sellPrice, weightedAverageCost);

        // Assert
        Assert.Equal(1000m, result); // (160 - 150) * 100 = 1000
    }

    [Fact]
    public void CalculateRealizedGainLoss_Loss_Success()
    {
        // Arrange
        int sellQuantity = 100;
        decimal sellPrice = 140m;
        decimal weightedAverageCost = 150m;

        // Act
        var result = _costCalc.CalculateRealizedGainLoss(
            sellQuantity, sellPrice, weightedAverageCost);

        // Assert
        Assert.Equal(-1000m, result); // (140 - 150) * 100 = -1000
    }

    [Fact]
    public void CalculateUnrealizedGainLoss_Profit_Success()
    {
        // Arrange
        decimal marketValue = 18000m;
        decimal totalCostBasis = 15000m;

        // Act
        var result = _costCalc.CalculateUnrealizedGainLoss(marketValue, totalCostBasis);

        // Assert
        Assert.Equal(3000m, result);
    }

    [Fact]
    public void CalculateUnrealizedGainLoss_Loss_Success()
    {
        // Arrange
        decimal marketValue = 12000m;
        decimal totalCostBasis = 15000m;

        // Act
        var result = _costCalc.CalculateUnrealizedGainLoss(marketValue, totalCostBasis);

        // Assert
        Assert.Equal(-3000m, result);
    }

    [Fact]
    public void AllocateLotsFifo_Success()
    {
        // Arrange
        var lots = new List<Lot>
        {
            new Lot { Id = Guid.NewGuid(), Quantity = 50, UnitCost = 100m, FifoOrder = 1 },
            new Lot { Id = Guid.NewGuid(), Quantity = 100, UnitCost = 110m, FifoOrder = 2 }
        };

        // Act
        var result = _costCalc.AllocateLotsFifo(lots, 120);

        // Assert
        Assert.Equal(2, result.Count);
        Assert.Equal(50, result[0].Quantity);
        Assert.Equal(70, result[1].Quantity);
    }

    [Fact]
    public void AllocateLotsFifo_InsufficientQuantity_Throws()
    {
        // Arrange
        var lots = new List<Lot>
        {
            new Lot { Id = Guid.NewGuid(), Quantity = 50, UnitCost = 100m, FifoOrder = 1 }
        };

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() =>
            _costCalc.AllocateLotsFifo(lots, 100));
    }

    [Fact]
    public void DetectQuantityVariance_NoVariance_ReturnsNull()
    {
        // Arrange
        var mismatches = _mismatchDetector.DetectMismatches(
            approvedQuantity: 100,
            executedQuantity: 100,
            approvedPrice: 150m,
            executedPrice: 150m,
            tradeDate: DateTime.UtcNow,
            expectedSettlementDate: DateTime.UtcNow.AddDays(2),
            actualSettlementDate: DateTime.UtcNow.AddDays(2),
            ledgerCostBasis: 15000m,
            calculatedCostBasis: 15000m,
            now: DateTime.UtcNow);

        // Assert
        Assert.Empty(mismatches);
    }

    [Fact]
    public void DetectQuantityVariance_VarianceDetected_ReturnsMismatch()
    {
        // Arrange
        // 100 -> 99 = 1% variance (exceeds 0.1% threshold)
        var mismatches = _mismatchDetector.DetectMismatches(
            approvedQuantity: 100,
            executedQuantity: 99,
            approvedPrice: 150m,
            executedPrice: 150m,
            tradeDate: DateTime.UtcNow,
            expectedSettlementDate: DateTime.UtcNow.AddDays(2),
            actualSettlementDate: DateTime.UtcNow.AddDays(2),
            ledgerCostBasis: 15000m,
            calculatedCostBasis: 14850m,
            now: DateTime.UtcNow);

        // Assert
        Assert.NotEmpty(mismatches);
        var quantityMismatch = mismatches.FirstOrDefault(m => m.Type == MismatchType.QuantityVariance);
        Assert.NotNull(quantityMismatch);
        Assert.Equal(MismatchSeverity.High, quantityMismatch.Severity);
    }

    [Fact]
    public void DetectPriceVariance_VarianceDetected_ReturnsMismatch()
    {
        // Arrange
        // 150 -> 153 = 2% variance (exceeds 2% threshold = at boundary)
        // Actually 150 -> 153.1 = 2.07% (exceeds)
        var mismatches = _mismatchDetector.DetectMismatches(
            approvedQuantity: 100,
            executedQuantity: 100,
            approvedPrice: 150m,
            executedPrice: 153.1m, // 2.07%
            tradeDate: DateTime.UtcNow,
            expectedSettlementDate: DateTime.UtcNow.AddDays(2),
            actualSettlementDate: DateTime.UtcNow.AddDays(2),
            ledgerCostBasis: 15000m,
            calculatedCostBasis: 15310m,
            now: DateTime.UtcNow);

        // Assert
        Assert.NotEmpty(mismatches);
        var priceMismatch = mismatches.FirstOrDefault(m => m.Type == MismatchType.PriceVariance);
        Assert.NotNull(priceMismatch);
        Assert.Equal(MismatchSeverity.Medium, priceMismatch.Severity);
    }

    [Fact]
    public void DetectSettlementDelay_DelayDetected_ReturnsMismatch()
    {
        // Arrange
        var expectedDate = DateTime.UtcNow.AddDays(-1);
        var actualDate = DateTime.UtcNow.AddDays(2); // 3 days late

        var mismatches = _mismatchDetector.DetectMismatches(
            approvedQuantity: 100,
            executedQuantity: 100,
            approvedPrice: 150m,
            executedPrice: 150m,
            tradeDate: DateTime.UtcNow.AddDays(-5),
            expectedSettlementDate: expectedDate,
            actualSettlementDate: actualDate,
            ledgerCostBasis: 15000m,
            calculatedCostBasis: 15000m,
            now: DateTime.UtcNow);

        // Assert
        Assert.NotEmpty(mismatches);
        var timingMismatch = mismatches.FirstOrDefault(m => m.Type == MismatchType.SettlementDelay);
        Assert.NotNull(timingMismatch);
    }

    [Fact]
    public void DetectCostBasisMismatch_MismatchDetected_ReturnsMismatch()
    {
        // Arrange
        var mismatches = _mismatchDetector.DetectMismatches(
            approvedQuantity: 100,
            executedQuantity: 100,
            approvedPrice: 150m,
            executedPrice: 150m,
            tradeDate: DateTime.UtcNow,
            expectedSettlementDate: DateTime.UtcNow.AddDays(2),
            actualSettlementDate: DateTime.UtcNow.AddDays(2),
            ledgerCostBasis: 15000.00m,
            calculatedCostBasis: 14999.50m, // $0.50 delta
            now: DateTime.UtcNow);

        // Assert
        Assert.NotEmpty(mismatches);
        var costMismatch = mismatches.FirstOrDefault(m => m.Type == MismatchType.CostBasisMismatch);
        Assert.NotNull(costMismatch);
    }

    [Fact]
    public void RequiresEscalation_HighSeverity_ReturnsTrue()
    {
        // Arrange
        var mismatches = new List<Mismatch>
        {
            new Mismatch { Severity = MismatchSeverity.High }
        };

        // Act
        var result = _mismatchDetector.RequiresEscalation(mismatches);

        // Assert
        Assert.True(result);
    }

    [Fact]
    public void RequiresEscalation_MediumOnly_ReturnsFalse()
    {
        // Arrange
        var mismatches = new List<Mismatch>
        {
            new Mismatch { Severity = MismatchSeverity.Medium }
        };

        // Act
        var result = _mismatchDetector.RequiresEscalation(mismatches);

        // Assert
        Assert.False(result);
    }

    [Fact]
    public void VerifyCostBasis_Correct_ReturnsTrue()
    {
        // Arrange
        decimal calculated = 15000.00m;
        decimal expected = 15000.01m;

        // Act
        var result = _costCalc.VerifyCostBasis(calculated, expected, tolerance: 0.05m);

        // Assert
        Assert.True(result);
    }

    [Fact]
    public void VerifyCostBasis_OutOfTolerance_ReturnsFalse()
    {
        // Arrange
        decimal calculated = 15000.00m;
        decimal expected = 14999.50m;

        // Act
        var result = _costCalc.VerifyCostBasis(calculated, expected, tolerance: 0.1m);

        // Assert
        Assert.False(result);
    }
}
