using Fast.Web.Models;

namespace Fast.Web.Logic;

public class ActualsCrudeSaveService(MyDbContext context)
{
    private readonly MyDbContext db = context;

    public async Task SaveItemsAsync(int actualTypeId, ActualsCommonListItem[] itemsToSave, int appUserId)
    {
        if (itemsToSave.Length == 0)
            return;

        await db.Database
            .CreateExecutionStrategy()
            .ExecuteAsync(() => ProcessItemsInTransactionAsync(actualTypeId, itemsToSave, appUserId));
    }

    private async Task ProcessItemsInTransactionAsync(int actualTypeId, ActualsCommonListItem[] itemsToSave, int appUserId)
    {
        var unlinkedMarketSupplies = new List<UnlinkedMarketSupply>();
        var isTransfer = actualTypeId == (int)Enums.ActualType.Transfer;

        using var transaction = await db.Database.BeginTransactionAsync();
        foreach (var item in itemsToSave)
        {
            await ProcessSingleItemAsync(
                item,
                actualTypeId,
                appUserId,
                isTransfer,
                unlinkedMarketSupplies,
                itemsToSave
            );
        }

        await db.SaveChangesAsync();
        await transaction.CommitAsync();
    }

    private async Task ProcessSingleItemAsync(
        ActualsCommonListItem item, int actualTypeId, int appUserId, bool isTransfer,
        List<UnlinkedMarketSupply> unlinkedMarketSupplies, ActualsCommonListItem[] allItems
    )
    {
        db.CrudeActuals.RemoveRange(db.CrudeActuals.Where(x =>
            x.ActualTypeId == actualTypeId &&
            x.SupplyNomId == item.SupplyNomId &&
            x.MarketNomId == item.MarketNomId &&
            x.LastTransferId == item.LastTransferId &&
            x.SaveDate == item.SaveDate
        ));

        var itemHasAnyValues =
            (item.IsVolumeEdited && item.ActualVolume.HasValue) ||
            (item.IsPriceEdited && item.Price.HasValue) ||
            (item.IsPriceAdjEdited && item.PriceAdj.HasValue) ||
            (item.IsTransportRateEdited && item.TransportRate.HasValue) ||
            (item.IsActualFeeEdited && item.ActualFee.HasValue) ||
            (item.IsAdjustmentEdited && item.Adjustment.HasValue) ||
            item.IsLinkEdited ||
            item.TneMeterId.HasValue;

        if (!itemHasAnyValues)
            return;

        var dbItem = CreateCrudeActualFromItem(item, actualTypeId, appUserId);

        if (isTransfer)
            HandleTransferLinking(dbItem, item, unlinkedMarketSupplies);

        db.CrudeActuals.Add(dbItem);

        if (isTransfer)
        {
            await SavePairedTransferItemAsync(
                db,
                dbItem,
                item,
                appUserId,
                allItems,
                unlinkedMarketSupplies
            );
        }
    }

    private static CrudeActual CreateCrudeActualFromItem(ActualsCommonListItem item, int actualTypeId, int appUserId)
    {
        var dbItem = new CrudeActual();
        dbItem.ActualTypeId = actualTypeId;
        dbItem.SupplyNomId = item.SupplyNomId;
        dbItem.MarketNomId = item.MarketNomId;
        dbItem.LastTransferId = item.LastTransferId;
        dbItem.Volume = item.ActualVolume;
        if (item.IsPriceEdited)
            dbItem.Price = item.Price;
        if (item.IsPriceAdjEdited)
            dbItem.Adder = item.PriceAdj;
        if (item.IsTransportRateEdited)
            dbItem.TransportRate = item.TransportRate;
        if (item.IsAdjustmentEdited)
            dbItem.Adjustment = item.Adjustment;
        if (item.IsActualFeeEdited)
            dbItem.ActualFee = item.ActualFee;
        dbItem.SaveDate = item.SaveDate;
        dbItem.SavedBy = appUserId;
        dbItem.TneMeterId = item.TneMeterId;

        return dbItem;
    }

    private static void HandleTransferLinking(CrudeActual dbItem, ActualsCommonListItem item, List<UnlinkedMarketSupply> unlinkedMarketSupplies)
    {
        var isNotPreviouslyUnlinked = !unlinkedMarketSupplies.Any(q =>
            q.SupplyNomId == item.SupplyNomId &&
            q.MarketNomId == item.MarketNomId &&
            q.SaveDate == item.SaveDate
        );

        // only set IsLinked if the item wasn't set as unlinked before
        dbItem.IsLinked = item.IsLinked && isNotPreviouslyUnlinked;

        if (!dbItem.IsLinked)
        {
            // track unlinked items
            unlinkedMarketSupplies.Add(new UnlinkedMarketSupply
            {
                SupplyNomId = item.SupplyNomId,
                MarketNomId = item.MarketNomId,
                SaveDate = item.SaveDate,
            });
        }
    }

    private static async Task SavePairedTransferItemAsync(
        MyDbContext db,
        CrudeActual editedDbItem,
        ActualsCommonListItem editedItem,
        int appUserId,
        IEnumerable<ActualsCommonListItem> itemsToSave,
        List<UnlinkedMarketSupply> unlinkedMarketSupplies)
    {
        var actualTypeId = (int)Enums.ActualType.Transfer;

        var pairedMarketSupplies = await GetPairedMarketSupplies(db, editedItem);
        foreach (var pairedMarketSupply in pairedMarketSupplies)
        {
            var isPairedItemDirectlyEdited = (
                from q in itemsToSave
                where q.SupplyNomId == pairedMarketSupply.SupplyNomId
                    && q.MarketNomId == pairedMarketSupply.MarketNomId
                    && q.ActualTypeId == actualTypeId
                    && q.SaveDate == editedDbItem.SaveDate
                select q
            ).Any();

            if (isPairedItemDirectlyEdited)
                continue;

            var dbPairedActual = await (
                from q in db.CrudeActuals
                where q.SupplyNomId == pairedMarketSupply.SupplyNomId
                    && q.MarketNomId == pairedMarketSupply.MarketNomId
                    && q.SaveDate == editedItem.SaveDate
                    && q.ActualTypeId == actualTypeId
                select q
            ).FirstOrDefaultAsync();

            if (dbPairedActual == null)
            {
                dbPairedActual = new CrudeActual();

                dbPairedActual.ActualTypeId = actualTypeId;
                dbPairedActual.SupplyNomId = pairedMarketSupply.SupplyNomId;
                dbPairedActual.MarketNomId = pairedMarketSupply.MarketNomId;
                dbPairedActual.SaveDate = editedDbItem.SaveDate;
                dbPairedActual.IsLinked = editedDbItem.IsLinked;
                dbPairedActual.SavedBy = appUserId;

                dbPairedActual.Volume = editedDbItem.Volume;
                dbPairedActual.TransportRate = editedDbItem.TransportRate;

                // track unlinked items
                if (!dbPairedActual.IsLinked)
                {
                    unlinkedMarketSupplies.Add(new UnlinkedMarketSupply
                    {
                        SupplyNomId = pairedMarketSupply.SupplyNomId,
                        MarketNomId = pairedMarketSupply.MarketNomId,
                        SaveDate = editedItem.SaveDate,
                    });
                }

                db.CrudeActuals.Add(dbPairedActual);
            }
            else // update existing item
            {
                var setIsLinked = GetPairedItemIsLinked(editedDbItem, pairedMarketSupply, unlinkedMarketSupplies);
                if (setIsLinked)
                {
                    dbPairedActual.IsLinked = true;
                    dbPairedActual.SavedBy = appUserId;

                    dbPairedActual.Volume = editedDbItem.Volume;
                    dbPairedActual.TransportRate = editedDbItem.TransportRate;
                }
                else // set as unlinked
                {
                    // if the paired item was already linked, then unlink it
                    if (dbPairedActual.IsLinked)
                    {
                        dbPairedActual.IsLinked = false;
                        dbPairedActual.SavedBy = appUserId;
                    }

                    // track unlinked items
                    unlinkedMarketSupplies.Add(new UnlinkedMarketSupply
                    {
                        SupplyNomId = pairedMarketSupply.SupplyNomId,
                        MarketNomId = pairedMarketSupply.MarketNomId,
                        SaveDate = editedItem.SaveDate,
                    });
                }
            }
        }
    }

    private class UnlinkedMarketSupply
    {
        public int SupplyNomId { get; set; }
        public int MarketNomId { get; set; }
        public DateOnly SaveDate { get; set; }
    }

    private static bool GetPairedItemIsLinked(
        CrudeActual editedDbItem,
        CrudeMarketSupply pairedMarketSupply,
        IEnumerable<UnlinkedMarketSupply> unlinkedMarketSupplies)
    {
        // only set the paired item as linked if the edited item
        // is linked and the paired item wasn't set as unlinked before.
        var isNotPreviouslyUnlinked = !(
            from q in unlinkedMarketSupplies
            where q.SupplyNomId == pairedMarketSupply.SupplyNomId
                && q.MarketNomId == pairedMarketSupply.MarketNomId
                && q.SaveDate == editedDbItem.SaveDate
            select q
        ).Any();

        var setPairedItemAsLinked = editedDbItem.IsLinked && isNotPreviouslyUnlinked;
        return setPairedItemAsLinked;
    }

    private static async Task<List<CrudeMarketSupply>> GetPairedMarketSupplies(MyDbContext db, ActualsCommonListItem editedItem)
    {
        List<CrudeMarketSupply> pairedMarketSupplies;

        var isEditedItemSupplySide = await (
            from q in db.CrudeSupplies
            where q.TransferDealId.HasValue
                && q.TransferDealId == editedItem.TransferDealId
                && q.Date == editedItem.Date
                && q.Id == editedItem.SupplyNomId
            select q
        ).AnyAsync();

        if (isEditedItemSupplySide)
        {
            // if the edited item is on the supply side then
            // get the MarketSupplies where it's on the market side
            pairedMarketSupplies = await (
                from q in db.CrudeMarketSupplies
                where q.Date == editedItem.Date
                    && q.MarketNom.TransferDealId.HasValue
                    && q.MarketNom.TransferDealId == editedItem.TransferDealId
                select q
            ).AsNoTracking().ToListAsync();
        }
        else
        {
            // if the edited item is on the market side then
            // get the MarketSupplies where it's on the supply side
            pairedMarketSupplies = await (
                from q in db.CrudeMarketSupplies
                where q.Date == editedItem.Date
                    && q.SupplyNom.TransferDealId.HasValue
                    && q.SupplyNom.TransferDealId == editedItem.TransferDealId
                select q
            ).AsNoTracking().ToListAsync();
        }
        return pairedMarketSupplies;
    }
}
