#nullable enable
using FruitBank.Common.Interfaces;
using FruitBank.Common.Models;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Services.Common;

namespace Nop.Plugin.Misc.FruitBankPlugin.Services;

public class FruitBankAttributeService(IGenericAttributeService genericAttributeService, IStoreContext storeContext)
{
    private const string NET_WEIGHT_KEY = nameof(IMeasuringAttributeValues.NetWeight);
    private const string IS_MEASURABLE_KEY = nameof(IMeasuringAttributeValues.IsMeasurable);


    public async Task<GenericAttribute?> GetGenericAttributeAsync<TEntity>(int entityId, string key, int storeId)
    {
        return (await genericAttributeService.GetAttributesForEntityAsync(entityId, typeof(TEntity).Name)).SingleOrDefault(ga => ga.StoreId == storeId && ga.Key == key);
    }

    public Task<TPropType?> GetGenericAttributeValueAsync<TEntity, TPropType>(int entityId, string key)
        => GetGenericAttributeValueAsync<TEntity, TPropType>(entityId, key, storeContext.GetCurrentStore().Id);

    public async Task<TPropType?> GetGenericAttributeValueAsync<TEntity, TPropType>(int entityId, string key, int storeId)
    {
        var ga = await GetGenericAttributeAsync<TEntity>(entityId, key, storeId);
        return ga == null ? default : CommonHelper.To<TPropType>(ga.Value);
    }

    public async Task<List<GenericAttribute>?> GetMeasuringAttributesAsync<TEntity>(int entityId, int storeId)
    {
        var measuringAttributes = (await genericAttributeService.GetAttributesForEntityAsync(entityId, typeof(TEntity).Name))
            .Where(ga => ga.StoreId == storeId && ga.Key is NET_WEIGHT_KEY or IS_MEASURABLE_KEY)
            .ToList();

        if (measuringAttributes.Count == 0) return null;
        if (measuringAttributes.Count != 2) throw new Exception($"FruitBankAttributeService->GetMeasuringAttributesAsync(); measuringAttributes.Count != 2; entityId: {entityId}");

        return measuringAttributes;
    }

    public Task<MeasuringAttributeValues?> GetMeasuringAttributeValuesAsync<TEntity>(int entityId)
        => GetMeasuringAttributeValuesAsync<TEntity>(entityId, storeContext.GetCurrentStore().Id);

    public async Task<MeasuringAttributeValues?> GetMeasuringAttributeValuesAsync<TEntity>(int entityId, int storeId)
    {
        var measuringAttributes = await GetMeasuringAttributesAsync<TEntity>(entityId, storeId);
        if (measuringAttributes == null) return null;

        var measuringAttributeValues = new MeasuringAttributeValues(
            entityId,
            CommonHelper.To<double>(measuringAttributes.Single(ga => ga.Key == NET_WEIGHT_KEY).Value),
            CommonHelper.To<bool>(measuringAttributes.Single(ga => ga.Key == IS_MEASURABLE_KEY).Value));

        return measuringAttributeValues;
    }

    public Task<bool> IsMeasurableEntityAsync<TEntity>(int entityId) => IsMeasurableEntityAsync<TEntity>(entityId, storeContext.GetCurrentStore().Id);
    public async Task<bool> IsMeasurableEntityAsync<TEntity>(int entityId, int storeId)
    {
        var measurableAttribute = await GetGenericAttributeAsync<TEntity>(entityId, IS_MEASURABLE_KEY, storeId);
        return measurableAttribute != null && CommonHelper.To<bool>(measurableAttribute.Value);
    }

    public async Task<MeasuringAttributeValues> InsertOrUpdateMeasuringAttributeValuesAsync<TEntity>(int entityId, double netWeight, bool isMeasurable, bool cumulativeWeightUpdate)
    {
        var measuringAttributeValues = new MeasuringAttributeValues
        {
            Id = entityId,
            NetWeight = netWeight,
            IsMeasurable = isMeasurable
        };

        await InsertOrUpdateMeasuringAttributeValuesAsync<TEntity>(measuringAttributeValues, cumulativeWeightUpdate);
        return measuringAttributeValues;
    }

    public Task InsertOrUpdateMeasuringAttributeValuesAsync<TEntity>(IMeasuringAttributeValues measuringAttributeValues, bool cumulativeWeightUpdate)
        => InsertOrUpdateMeasuringAttributeValuesAsync<TEntity>(measuringAttributeValues, cumulativeWeightUpdate, storeContext.GetCurrentStore().Id);

    public async Task InsertOrUpdateMeasuringAttributeValuesAsync<TEntity>(IMeasuringAttributeValues measuringAttributeValues, bool cumulativeWeightUpdate, int storeId)
    {
        if (!measuringAttributeValues.HasMeasuringValues()) throw new Exception($"FruitBankAttributeService->InsertOrUpdateMeasuringAttributeValuesAsync; measuringAttributeValues.HasMeasuringValues() == false; keyGroup: {typeof(TEntity).Name}; values: {measuringAttributeValues}");

        var entityId = measuringAttributeValues.Id;
        var measuringAttributes = await GetMeasuringAttributesAsync<TEntity>(entityId, storeId);

        if (measuringAttributes == null)
        {
            await InsertGenericAttributeAsync<TEntity, double>(entityId, NET_WEIGHT_KEY, double.Round(measuringAttributeValues.NetWeight, 1), storeId);
            await InsertGenericAttributeAsync<TEntity, bool>(entityId, IS_MEASURABLE_KEY, measuringAttributeValues.IsMeasurable, storeId);

            return;
        }

        await UpdateMeasuringWeightAttributeValueAsync(measuringAttributes.Single(ma => ma.Key == NET_WEIGHT_KEY), measuringAttributeValues.NetWeight, cumulativeWeightUpdate);

        //TODO: ezzel mi legyen? - J.
        //await UpdateGenericAttributeAsync(measuringAttributes.Single(ma => ma.Key == IS_MEASURABLE_KEY), measuringAttributeValues.IsMeasurable);
    }

    /// <returns>Inserted NetWeight</returns>
    private async Task<double> UpdateMeasuringWeightAttributeValueAsync(GenericAttribute genericAttribute, double newWeightValue, bool cumulativeWeightUpdate)
    {
        var newNetWeight = double.Round((cumulativeWeightUpdate ? CommonHelper.To<double>(genericAttribute.Value) + newWeightValue : newWeightValue), 1);
        await UpdateGenericAttributeAsync(genericAttribute, newNetWeight);

        return newNetWeight;
    }

    /// <returns>Inserted NetWeight</returns>
    public async Task<double> InsertOrUpdateNetWeightAsync<TEntity>(int entityId, double netWeight, bool cumulativeWeightUpdate)
        => await InsertOrUpdateNetWeightAsync<TEntity>(entityId, netWeight, cumulativeWeightUpdate, (await storeContext.GetCurrentStoreAsync()).Id);

    /// <returns>Inserted NetWeight</returns>
    public async Task<double> InsertOrUpdateNetWeightAsync<TEntity>(int entityId, double netWeight, bool cumulativeWeightUpdate, int storeId)
    {
        var netWeightGa = await GetGenericAttributeAsync<TEntity>(entityId, NET_WEIGHT_KEY, storeId);

        if (netWeightGa != null) return await UpdateMeasuringWeightAttributeValueAsync(netWeightGa, netWeight, cumulativeWeightUpdate);

        netWeight = double.Round(netWeight, 1);
        await InsertGenericAttributeAsync<TEntity, double>(entityId, NET_WEIGHT_KEY, double.Round(netWeight, 1), storeId);

        return netWeight;
    }

    public async Task DeleteAllMeasuringAttributesAsync<TEntity>(int entityId, int storeId)
    {
        var measuringAttributes = await GetMeasuringAttributesAsync<TEntity>(entityId, storeId);
        if (measuringAttributes == null) return;

        foreach (var measuringAttribute in measuringAttributes)
        {
            await genericAttributeService.DeleteAttributeAsync(measuringAttribute);
        }
    }

    public Task InsertOrUpdateGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType value)
        => InsertOrUpdateGenericAttributeAsync<TEntity, TPropType>(entityId, key, value, storeContext.GetCurrentStore().Id);

    public async Task InsertOrUpdateGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType value, int storeId)
    {
        var ga = await GetGenericAttributeAsync<TEntity>(entityId, key, storeId);
        
        if (ga == null) await InsertGenericAttributeAsync<TEntity, TPropType>(entityId, key, value, storeId);
        else await UpdateGenericAttributeAsync(ga, value);
    }

    public Task InsertGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType value)
        => InsertGenericAttributeAsync<TEntity, TPropType>(entityId, key, value, storeContext.GetCurrentStore().Id);

    public async Task InsertGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType value, int storeId)
    {
        var genericAttribute = new GenericAttribute
        {
            Key = key,
            EntityId = entityId,
            KeyGroup = typeof(TEntity).Name,
            Value = CommonHelper.To<string>(value),
            StoreId = storeId
        };

        await genericAttributeService.InsertAttributeAsync(genericAttribute);
    }

    public Task UpdateGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType value)
        => UpdateGenericAttributeAsync<TEntity, TPropType>(entityId, key, value, storeContext.GetCurrentStore().Id);

    public async Task UpdateGenericAttributeAsync<TEntity, TPropType>(int entityId, string key, TPropType newValue, int storeId)
    {
        var ga = await GetGenericAttributeAsync<TEntity>(entityId, key, storeId);
        await UpdateGenericAttributeAsync(ga!, newValue);
    }

    public async Task UpdateGenericAttributeAsync<TPropType>(GenericAttribute genericAttribute, TPropType newValue)
    {
        genericAttribute.Value = CommonHelper.To<string>(newValue);
        await genericAttributeService.UpdateAttributeAsync(genericAttribute);
    }

    public Task DeleteGenericAttributeAsync<TEntity>(int entityId, string key)
        => DeleteGenericAttributeAsync<TEntity>(entityId, key, storeContext.GetCurrentStore().Id);

    public async Task DeleteGenericAttributeAsync<TEntity>(int entityId, string key, int storeId)
    {
        var ga = await GetGenericAttributeAsync<TEntity>(entityId, key, storeId);
        if (ga == null) return;

        await DeleteGenericAttributeAsync(ga);
    }

    public async Task DeleteGenericAttributeAsync(GenericAttribute genericAttribute)
        => await genericAttributeService.DeleteAttributeAsync(genericAttribute);

    public async Task DeleteGenericAttributesAsync(IList<GenericAttribute> genericAttributes)
        => await genericAttributeService.DeleteAttributesAsync(genericAttributes);
}