namespace KArtSell.Modules.ModelOperations.PortfolioReconciliation;

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Calculates weighted average cost, unrealized gain/loss, and realized gain/loss.
/// Supports FIFO/LIFO lot tracking.
/// </summary>
public class CostBasisCalculator
{
    /// <summary>
    /// Calculates weighted average cost for a new trade.
    /// </summary>
    public decimal CalculateWeightedAverageCost(
        int previousQuantity,
        decimal previousCostBasis,
        int buyQuantity,
        decimal buyPrice)
    {
        if (previousQuantity + buyQuantity == 0)
            return 0m;

        var totalCost = previousCostBasis + (buyQuantity * buyPrice);
        var totalQuantity = previousQuantity + buyQuantity;
        return totalCost / totalQuantity;
    }

    /// <summary>
    /// Calculates realized gain/loss for a sale.
    /// </summary>
    public decimal CalculateRealizedGainLoss(
        int sellQuantity,
        decimal sellPrice,
        decimal weightedAverageCost)
    {
        return (sellPrice - weightedAverageCost) * sellQuantity;
    }

    /// <summary>
    /// Calculates unrealized gain/loss for open positions.
    /// </summary>
    public decimal CalculateUnrealizedGainLoss(
        decimal marketValue,
        decimal totalCostBasis)
    {
        return marketValue - totalCostBasis;
    }

    /// <summary>
    /// Calculates gain/loss per share.
    /// </summary>
    public decimal CalculateGainLossPerShare(
        decimal marketPrice,
        decimal weightedAverageCost)
    {
        return marketPrice - weightedAverageCost;
    }

    /// <summary>
    /// FIFO lot selection for a sale.
    /// </summary>
    public List<LotAllocation> AllocateLotsFifo(
        List<Lot> openLots,
        int quantityToSell)
    {
        var allocations = new List<LotAllocation>();
        var remaining = quantityToSell;

        foreach (var lot in openLots.OrderBy(l => l.FifoOrder))
        {
            if (remaining == 0) break;

            var quantity = Math.Min(lot.Quantity, remaining);
            allocations.Add(new LotAllocation
            {
                LotId = lot.Id,
                Quantity = quantity,
                UnitCost = lot.UnitCost
            });

            remaining -= quantity;
        }

        if (remaining > 0)
            throw new InvalidOperationException($"Insufficient quantity. Requested: {quantityToSell}, Available: {quantityToSell - remaining}");

        return allocations;
    }

    /// <summary>
    /// LIFO lot selection for a sale.
    /// </summary>
    public List<LotAllocation> AllocateLotsLifo(
        List<Lot> openLots,
        int quantityToSell)
    {
        var allocations = new List<LotAllocation>();
        var remaining = quantityToSell;

        foreach (var lot in openLots.OrderByDescending(l => l.FifoOrder))
        {
            if (remaining == 0) break;

            var quantity = Math.Min(lot.Quantity, remaining);
            allocations.Add(new LotAllocation
            {
                LotId = lot.Id,
                Quantity = quantity,
                UnitCost = lot.UnitCost
            });

            remaining -= quantity;
        }

        if (remaining > 0)
            throw new InvalidOperationException($"Insufficient quantity. Requested: {quantityToSell}, Available: {quantityToSell - remaining}");

        return allocations;
    }

    /// <summary>
    /// Verifies cost basis calculation accuracy (for audit).
    /// </summary>
    public bool VerifyCostBasis(
        decimal calculatedBasis,
        decimal expectedBasis,
        decimal tolerance = 0.01m)
    {
        var delta = Math.Abs(calculatedBasis - expectedBasis);
        return delta <= tolerance;
    }
}

public class Lot
{
    public Guid Id { get; set; }
    public Guid HoldingId { get; set; }
    public DateTime PurchaseDate { get; set; }
    public int Quantity { get; set; }
    public decimal UnitCost { get; set; }
    public decimal TotalCost { get; set; }
    public string Status { get; set; } = "OPEN";
    public int FifoOrder { get; set; }
}

public class LotAllocation
{
    public Guid LotId { get; set; }
    public int Quantity { get; set; }
    public decimal UnitCost { get; set; }
    public decimal RealizedGainLoss { get; set; }
}
