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

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

public class AnimeTitle : Entity
{
    public CommonProperties.CommonProperties CommonProperties { get; protected set; } = default!;

    private readonly List<AnimeItem> _items = [];
    public IReadOnlyCollection<AnimeItem> Items => _items.ToList().AsReadOnly();

    public bool Completed { get; private set; }
    public TimeSpan ExpirationTime { get; private set; }

    private AnimeTitle() { }

    private AnimeTitle(Models.AnimeTitle title)
    {
        Id = title.Id;
        Deleted = title.Deleted;
        CommonProperties = MediaContent.CommonProperties.CommonProperties.New(title.CommonProperties);
        Completed = title.Completed;
        ExpirationTime = title.ExpirationTime;
        foreach(var item in title.Items)
        {
            if (item is Models.AnimeEpisode episode)
            {
                _items.Add(AnimeEpisode.New(this, episode));
            }
            else if (item is Models.AnimeSeason season)
            {
                _items.Add(AnimeSeason.New(this, season));
            }
        }
    }

    public AnimeTitle(IEnumerable<NameItem> names)
    {
        CommonProperties = new CommonProperties.CommonProperties(names);
    }

    public static AnimeTitle New(Models.AnimeTitle title) => new(title);

    public AnimeEpisode AddEpisode(AnimeEpisodeType type, IEnumerable<NameItem> names, int? number = null, TimeSpan? duration = null) =>
        CreateAndAddEpisode(type, names, number: number, duration: duration);

    public AnimeEpisode AddEpisodeAsVariant(AnimeEpisodeType type, IEnumerable<NameItem> names, ushort order, int? number = null, TimeSpan? duration = null) =>
        CreateAndAddEpisode(type, names, order, number:number, duration: duration);

    private AnimeEpisode CreateAndAddEpisode(AnimeEpisodeType type, IEnumerable<NameItem> names,
        ushort? order = null, ushort? variant = null, int? number = null, TimeSpan? duration = null)
    {
        StructureAction(() =>
        {
            (order, variant) = AnimeItem.GetOrderAndVariant(_items, order, variant);
            number ??= _items.OfType<AnimeEpisode>().Select(q => q.Number).DefaultIfEmpty(0).Max() + 1;
            _items.Add(new AnimeEpisode(this, null, type, names, order.Value, variant.Value, number, duration));
            CheckIfCompleted();
        });
        return _items.OfType<AnimeEpisode>().OrderByDescending(q => q.Order).ThenByDescending(q => q.Variant).First();
    }

    public void RemoveEpisode(ushort order, ushort variant)
    {
        var episode = _items.OfType<AnimeEpisode>().FirstOrDefault(q => q.Order == order && q.Variant == variant) ??
            throw new Exception("EpisodeNotFoundException");
        _items.Remove(episode);
    }

    public AnimeSeason AddSeason()
    {
        StructureAction(() =>
        {
            (ushort order, _) = AnimeItem.GetOrderAndVariant(_items);
            //var number = _items.OfType<AnimeSeason>().Select(q => q.Number).DefaultIfEmpty(-1).Max() + 1;
            var number = _items.OfType<AnimeSeason>().Select(q => q.Number).DefaultIfEmpty(0).Max() + 1;
            var season = new AnimeSeason(this, order, number);
            _items.Add(season);
        });
        return _items.OfType<AnimeSeason>().OrderByDescending(q => q.Order).ThenByDescending(q => q.Variant).First();
    }

    public AnimeSeason AddSeason(IEnumerable<NameItem> names, int? number = null)
    {
        StructureAction(() =>
        {
            (ushort order, _) = AnimeItem.GetOrderAndVariant(_items);
            number ??= _items.OfType<AnimeSeason>().Select(q => q.Number).DefaultIfEmpty(0).Max() + 1;
            var season = new AnimeSeason(this, names, order, number);
            _items.Add(season);
        });
        return _items.OfType<AnimeSeason>().OrderByDescending(q => q.Order).ThenByDescending(q => q.Variant).First();
    }

    /*
    public void AddSeasonAsVariant(AnimeEpisodeType episodeType, ushort order)
    {
        if (order < 0 || order > _shadow.items.Select(q => q.order).DefaultIfEmpty<ushort>(0).Max())
            throw new ArgumentOutOfRangeException(nameof(order));

        var episode = new AnimeEpisodeShadow
        {
            type = episodeType,
            order = order,
            variant = AnimeItem.GetNewVariant(_shadow.items, order),
            number = _shadow.items.OfType<AnimeEpisodeShadow>().FirstOrDefault(q => q.order == order)?.number,
        };
        _shadow.items.Add(episode);
        CheckIsCompleted();
    }
    */

    //public void RemoveSeason(ushort order, ushort variant)
    public void RemoveSeason(ushort order)
    {
        var season = _items.OfType<AnimeSeason>().FirstOrDefault(q => q.Order == order) ??
            throw new Exception("SeasonNotFoundException");
        _items.Remove(season);
    }

    public void SetCompleted() => Completed = true;

    public void SetNotCompleted() => Completed = false;

    public void SetExpirationTime(TimeSpan value) => StructureAction(() => ExpirationTime = value);

    public void Delete() => Deleted = true;

    internal void CheckIfCompleted()
    {
        var itemsQuery = Items.AsQueryable();
        var ucompletedSeasons = itemsQuery
            .Any(q => !q.CommonProperties.ReleaseDate.HasValue);
        var lastEpisodeReleaseDate = itemsQuery
            .OrderByDescending(q => q.CommonProperties.ReleaseDate)
            .FirstOrDefault()?.CommonProperties.ReleaseDate;
        if (ucompletedSeasons || lastEpisodeReleaseDate >= DateTime.UtcNow - ExpirationTime) SetNotCompleted();
        else SetCompleted();
    }


    protected void StructureAction(Action action)
    {
        action();
        CheckIfCompleted();
    }
}