using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using FruitBank.Common.Entities;
using FruitBank.Common.Enums;
using FruitBank.Common.Interfaces;
using LinqToDB.Mapping;
using Mango.Nop.Core.Dtos;
using Newtonsoft.Json;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;

namespace FruitBank.Common.Dtos;

[AcBinarySerializable(false, true, false, true, false, false)]
[LinqToDB.Mapping.Table(Name = nameof(OrderItem))]
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(OrderItem))]
[ToonDescription("Order item with measurements, pallets, and validation", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(OrderItem)])]
public class OrderItemDto : MgOrderItemDto<ProductDto>, IOrderItemDto
{
    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    private static Expression<Func<OrderItemDto, GenericAttributeDto, bool>> RelationWithGenericAttribute => (orderItemDto, genericAttributeDto) =>
        orderItemDto.Id == genericAttributeDto.EntityId && genericAttributeDto.KeyGroup == nameof(OrderItem);


    [Association(ThisKey = nameof(Id), OtherKey = nameof(GenericAttribute.EntityId), ExpressionPredicate = nameof(RelationWithGenericAttribute), CanBeNull = true)]
    public List<GenericAttributeDto> GenericAttributes { get; set; }

    [Association(ThisKey = nameof(Id), OtherKey = nameof(OrderItemPallet.OrderItemId), CanBeNull = true)]
    public List<OrderItemPallet> OrderItemPallets { get; set; }

    [Association(ThisKey = nameof(OrderId), OtherKey = nameof(OrderDto.Id), CanBeNull = false)]
    public OrderDto OrderDto { get; set; }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(Purpose = "Status flag", BusinessRule = "get => IsMeasuredAndValid()")]
    public bool IsMeasured
    {
        get => IsMeasuredAndValid();
        set => throw new Exception($"OrderItemDto.IsMeasured not set");
    }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(Purpose = "Status flag", BusinessRule = "get => ProductDto!.IsMeasurable")]
    public bool IsMeasurable
    {
        get => ProductDto!.IsMeasurable;
        set => throw new Exception($"OrderItemDto.IsMeasurable not set");
    }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => OrderItemPallets.Sum(x => x.TrayQuantity)")]
    public int TrayQuantity
    {
        get => OrderItemPallets.Sum(x => x.TrayQuantity);
        set => throw new Exception($"OrderItemDto.TrayQuantity not set");
    }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")]
    public double NetWeight
    {
        get
        {
            return double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1);
            //return CommonHelper.To<double>(GenericAttributes.SingleOrDefault(x => x.Key == nameof(IMeasuringNetWeight.NetWeight))?.Value ?? "0");
        }
        set
        {
            throw new Exception($"OrderItemDto.NetWeight not set");
            ////Direkt legyen exception! - J.
            //var ga = GenericAttributes?.SingleOrDefault(x => x.Key == nameof(IMeasuringNetWeight.NetWeight))!;
            //ga.Value = value.ToString(CultureInfo.InvariantCulture);
        }
    }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1)")]
    public double GrossWeight
    {
        get
        {
            return double.Round(OrderItemPallets.Sum(x => x.NetWeight), 1);
            //CommonHelper.To<double>(GenericAttributes.SingleOrDefault(x => x.Key == nameof(IMeasuringGrossWeight.GrossWeight))?.Value ?? "0");
        }
        set
        {
            throw new Exception($"OrderItemDto.GrossWeight not set");
            //Direkt legyen exception! - J.
            var ga = GenericAttributes?.SingleOrDefault(x => x.Key == nameof(IMeasuringGrossWeight.GrossWeight))!;
            ga.Value = value.ToString(CultureInfo.InvariantCulture);
        }
    }

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d")]
    public double AverageWeight => IsMeasurable && OrderItemPallets.Count > 0 ? double.Round(OrderItemPallets.Sum(oip => oip.AverageWeight) / OrderItemPallets.Count, 1) : 0d;

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0")]
    public double AverageWeightDifference => IsMeasurable ? double.Round(ProductDto!.AverageWeight - AverageWeight, 1) : 0;
    
    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => !IsMeasurable || (ProductDto!.AverageWeight > 0 && ((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold)")]
    public bool AverageWeightIsValid => !IsMeasurable || (ProductDto!.AverageWeight > 0 && Math.Abs((AverageWeightDifference / ProductDto!.AverageWeight) * 100) < ProductDto!.AverageWeightTreshold);

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(Purpose = "Status flag", BusinessRule = "get => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited)")]
    public bool IsAudited => OrderItemPallets.Count > 0 && OrderItemPallets.All(oip => oip.IsAudited);

    [NotColumn, NotMapped, JsonIgnore, System.Text.Json.Serialization.JsonIgnore]
    [ToonDescription(BusinessRule = "get => complex conditional logic based on IsAudited, IsMeasured, and OrderItemPallets status")]
    public MeasuringStatus MeasuringStatus
    {
        get
        {
            if (IsAudited) return MeasuringStatus.Audited;
            if (IsMeasured) return MeasuringStatus.Finnished;

            return OrderItemPallets.Any(oip => oip.MeasuringStatus >= MeasuringStatus.Started) ? MeasuringStatus.Started : MeasuringStatus.NotStarted;
        }
    }

    public OrderItemDto() : base()
    {
    }

    public OrderItemDto(int orderItemId) : base(orderItemId)
    {
    }

    public OrderItemDto(OrderItem orderItem) : base(orderItem)
    {
    }

    public void CopyEntityValuesToDto(OrderItem entity, ProductDto productDto, List<OrderItemPallet> orderItemPallets)
    {
        base.CopyEntityValuesToDto(entity, productDto);

        InitializeOrderItemPallets(orderItemPallets);
    }

    public void InitializeOrderItemPallets(List<OrderItemPallet> orderItemPallets)
    {
        OrderItemPallets = orderItemPallets;
    }

    public bool IsMeasuredAndValid() => Id > 0 && IsValidMeasuringValues() && OrderItemPallets.All(oip => oip.IsMeasuredAndValid(IsMeasurable));
    public bool IsValidMeasuringValues() => OrderItemPallets.Count > 0 && (!IsMeasurable || NetWeight > 0) && TrayQuantity == Quantity;

    public bool IsOtherMeasuringInProgress(int? customerId)
        => customerId.GetValueOrDefault(0) != 0 && OrderItemPallets.Any(oip => oip.Id > 0 && oip.CreatorId.GetValueOrDefault(0) != customerId);
}