using Modules.Library.Domain.Entities.MediaContent.CommonProperties;

namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;

public abstract class AnimeItem : Entity, IOrderableItem
{
    protected readonly AnimeTitle _title = default!;

    public CommonProperties.CommonProperties CommonProperties { get; protected set; } = default!;
    public ushort Order { get; protected set; }
    public ushort Variant { get; protected set; }
    public bool Completed { get; protected set; }
    public override bool Deleted { get; protected set; }
    public TimeSpan ExpirationTime { get; protected set; }


    internal static Action<AnimeItem, ushort> SetOrderAction => (item, value) => item.Order = value;
    internal static Action<AnimeItem, ushort> SetVariantAction => (item, value) => item.Variant = value;


    private protected AnimeItem() { }

    internal AnimeItem(AnimeTitle title)
    {
        _title = title;
        CommonProperties = new CommonProperties.CommonProperties();
    }

    internal AnimeItem(AnimeTitle title, IEnumerable<NameItem> names)
    {
        _title = title;
        CommonProperties = new CommonProperties.CommonProperties(names);
    }

    public abstract void SetOrder(ushort order);

    public abstract void SetVariant(ushort variant);

    private static ushort GetNewOrder(IEnumerable<AnimeItem> items) =>
        (ushort)(items.Select(q => (int)q.Order).DefaultIfEmpty(-1).Max() + 1);

    private static ushort GetNewVariant(IEnumerable<AnimeItem> items, ushort order) =>
        (ushort)(items.Where(q => q.Order == order)
        .Select(q => (int)q.Variant).DefaultIfEmpty(-1).Max() + 1);

    internal static (ushort Order, ushort Variant) GetOrderAndVariant(IEnumerable<AnimeItem> episodes, ushort? order = null, ushort? variant = null)
    {
        if (!order.HasValue) order = GetNewOrder(episodes);
        else if (order < 0 || order > episodes.Select(q => q.Order).DefaultIfEmpty((ushort)0).Max())
            throw new ArgumentOutOfRangeException(nameof(order));

        if (!variant.HasValue) variant ??= GetNewVariant(episodes, order.Value);
        else if (variant < 0 || variant > episodes.Select(q => q.Variant).DefaultIfEmpty((ushort)0).Max())
            throw new ArgumentOutOfRangeException(nameof(order));
        return (order.Value, variant.Value);
    }

    /*
    internal protected void SortItems(IEnumerable<AnimeItem> items) =>
        base.SortItems(items, SetVariantAction, SetOrderAction);
    */

    private protected virtual void SetItemOrder(IEnumerable<AnimeItem> items, ushort lastOrder, ushort newOrder)
    {
        if (lastOrder < 0 || lastOrder >= items.Count()) throw new ArgumentOutOfRangeException(nameof(lastOrder));
        if (newOrder < 0 || newOrder >= items.Count()) throw new ArgumentOutOfRangeException(nameof(newOrder));
        var minValue = Math.Min(lastOrder, newOrder);
        var maxValue = Math.Max(lastOrder, newOrder);
        var itemOrderGroups = items.GroupBy(q => q.Order)
            .Select(q => new
            {
                Order = q.Key,
                Items = items.Where(x => x.Order == q.Key)
                .OrderBy(x => x.Variant).ToList()
            })
            .Where(q => q.Order >= minValue && q.Order <= maxValue).ToList();
        var targetItem = itemOrderGroups.First(q => q.Order == newOrder);
        bool seen = false;
        for (int i = 0; i < itemOrderGroups.Count; i++)
        {
            if (object.ReferenceEquals(itemOrderGroups[i], targetItem))
            {
                itemOrderGroups[i].Items.ForEach(q => q.Order = newOrder);
                //itemOrderGroups[i].Order = newOrder;
                seen = true;
            }
            else if (seen && itemOrderGroups[i].Order <= newOrder)
            {
                itemOrderGroups[i].Items.ForEach(q => q.Order = (ushort)(itemOrderGroups[i].Order - 1));
                //orderedItems[i].Order--;// move it left
            }
            else if (itemOrderGroups[i].Order >= newOrder)
            {
                itemOrderGroups[i].Items.ForEach(q => q.Order = (ushort)(itemOrderGroups[i].Order + 1));
                //orderedItems[i].Order++; // move it right
            }
        }
    }

    private protected virtual void SetItemVariant(IEnumerable<AnimeItem> items, ushort order, ushort lastVariant, ushort newVariant)
    {
        if (order < 0 || order >= items.GroupBy(q => q.Order).Count()) throw new ArgumentOutOfRangeException(nameof(order));
        var variantItems = items.Where(q => q.Order == order).ToList();
        if (lastVariant < 0 || lastVariant >= variantItems.Count()) throw new ArgumentOutOfRangeException(nameof(lastVariant));
        if (newVariant < 0 || newVariant >= variantItems.Count()) throw new ArgumentOutOfRangeException(nameof(newVariant));
        var minValue = Math.Min(lastVariant, newVariant);
        var maxValue = Math.Max(lastVariant, newVariant);
        var orderedItems = variantItems.Where(q => q.Variant >= minValue && q.Variant <= maxValue).ToList();
        var targetItem = orderedItems.First(q => q.Variant == newVariant);
        bool seen = false;
        for (int i = 0; i < orderedItems.Count; i++)
        {
            if (object.ReferenceEquals(orderedItems[i], targetItem))
            {
                orderedItems[i].Variant = newVariant;
                seen = true;
            }
            else if (seen && orderedItems[i].Variant <= newVariant)
            {
                orderedItems[i].Variant--;// move it left
            }
            else if (orderedItems[i].Variant >= newVariant)
            {
                orderedItems[i].Variant++; // move it right
            }
        }
    }


    public virtual void SetCompleted() => StructureAction(() => Completed = true);
    public virtual void SetNotCompleted() => StructureAction(() => Completed = true);
    public virtual void SetExpirationTime(TimeSpan value) => StructureAction(() => ExpirationTime = value);

    public virtual void Delete() => StructureAction(() => Deleted = true);

    protected abstract void StructureAction(Action action);
}