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

public class CommonProperties : ValueObject
{
    private readonly List<NameItem> _names = [];
    public IReadOnlyCollection<NameItem> Names => _names.AsReadOnly();
    public MediaInfo? Preview { get; private set; }

    private readonly List<Description> _descriptions = [];
    public IReadOnlyCollection<Description> Descriptions => _descriptions.AsReadOnly();

    private readonly List<GenreProportion> _genres = [];
    public IReadOnlyCollection<GenreProportion> Genres => _genres.AsReadOnly();

    private readonly List<MediaInfo> _relatedContent = [];
    public IReadOnlyCollection<MediaInfo> RelatedContent => _relatedContent.AsReadOnly();


    public DateTimeOffset? AnnouncementDate { get; private set; }
    public DateTimeOffset? EstimatedReleaseDate { get; private set; }
    public DateTimeOffset? ReleaseDate { get; private set; }

    internal CommonProperties() { }
    private CommonProperties(Models.CommonProperties commonProperties)
    {
        Preview = commonProperties.Preview != null
            ? new MediaInfo(commonProperties.Preview.ObjectId, commonProperties.Preview.Type, commonProperties.Preview.ContentType)
            : null;
        AnnouncementDate = commonProperties.AnnouncementDate;
        EstimatedReleaseDate = commonProperties.EstimatedReleaseDate;
        ReleaseDate = commonProperties.ReleaseDate;
        _names.AddRange(commonProperties.Names.Select(NameItem.New));
        _descriptions.AddRange(commonProperties.Descriptions.Select(Description.New));
        _genres.AddRange(commonProperties.Genres.Select(GenreProportion.New));
        _relatedContent.AddRange(commonProperties.RelatedContent.Select(q => new MediaInfo(q.ObjectId, q.Type, q.ContentType)));
    }

    internal CommonProperties(IEnumerable<NameItem> names)
    {
        foreach (var name in names)
        {
            AddName(name.Type, name.LanguageId, name.Value);
        }
    }

    internal static CommonProperties New(Models.CommonProperties commonProperties) => new(commonProperties);

    #region Name
    public void AddName(NameType nameType, Guid languageId, string value)
    {
        switch (nameType)
        {
            case NameType.Original:
                AddNameOriginal(languageId, value);
                break;
            case NameType.OriginalInAnotherLanguage:
                AddNameOriginalInAnotherLanguage(languageId, value);
                break;
            case NameType.Translation:
                AddNameTranslation(languageId, value);
                break;
            case NameType.Abbreviation:
                AddNameAbbreviation(languageId, value);
                break;
            default:
                throw new NotImplementedException();
        }
    }

    private void AddNameOriginal(Guid languageId, string value)
    {
        if (_names.Any(q => q.Type == NameType.Original)) throw new Exception("Original name is already exist.");
        var name = new NameItem(languageId, NameType.Original, value);
        _names.Add(name);
    }

    private void AddNameOriginalInAnotherLanguage(Guid languageId, string value)
    {
        if (_names.FirstOrDefault(q => q.Type == NameType.Original)?.LanguageId == languageId)
            throw new Exception("Language must not match original name language");

        if (_names.Any(q => q.Type == NameType.OriginalInAnotherLanguage && q.LanguageId == languageId))
            throw new Exception("Name in following language is already exist.");

        var name = new NameItem(languageId, NameType.OriginalInAnotherLanguage, value);
        _names.Add(name);
    }

    private void AddNameTranslation(Guid languageId, string value)
    {
        var name = new NameItem(languageId, NameType.Translation, value);
        _names.Add(name);
    }

    private void AddNameAbbreviation(Guid languageId, string value)
    {
        var name = new NameItem(languageId, NameType.Abbreviation, value);
        _names.Add(name);
    }

    public void RemoveName(NameItem nameItem)
    {
        var name = GetName(nameItem);
        if (name.Type == NameType.Original) throw new Exception($"Unable to remove original name");
        _names.Remove(name);
    }

    public void SetNameValue(NameItem nameItem, Guid? languageId, string? value)
    {
        var name = GetName(nameItem);
        var newValue = value?.Trim();
        if (string.IsNullOrWhiteSpace(newValue)) throw new ArgumentNullException(nameof(value));
        if (nameItem.Type != NameType.Original && _names.Any(q => q.LanguageId == (languageId ?? nameItem.LanguageId) && q.Value == (newValue ?? nameItem.Value)))
            throw new Exception("Name item with same language and same value is already exists");
        else if (nameItem.Type == NameType.Original && languageId.HasValue && _names.Any(q => q.LanguageId == languageId && q.Type == NameType.OriginalInAnotherLanguage))
            throw new Exception("Could not change original name language to one of \"original in another language\" item's one");
        if (languageId.HasValue) name.SetLanguage(languageId.Value);
        if (!string.IsNullOrWhiteSpace(newValue)) name.SetValue(newValue);
    }

    private NameItem GetName(NameItem name) => _names.FirstOrDefault(q => q == name) ??
        throw new Exception($"Name with is not found");
    #endregion

    public void SetPreview(string objectId, MediaInfoType type, string contentType)
    {
        if (string.IsNullOrWhiteSpace(objectId)) throw new ArgumentNullException(nameof(objectId));
        Preview ??= new MediaInfo(objectId, type, contentType);
        Preview.SetObjectId(objectId);
        Preview.SetType(type);
        Preview.SetContentType(contentType);
    }
    public void DeletePreview() => Preview = null;

    #region Description
    public void AddOriginalDescription(Guid languageId, string value)
    {
        if (_descriptions.Any(q => q.IsOriginal)) throw new Exception("Original description is already exist.");
        var description = new Description(languageId, true, value);
        _descriptions.Add(description);
    }

    public void AddNotOriginalDescription(Guid languageId, string value)
    {
        var description = new Description(languageId, false, value);
        _descriptions.Add(description);
    }

    public void RemoveDescription(Description descriptionItem)
    {
        var description = GetDescription(descriptionItem);
        if (description.IsOriginal) throw new Exception($"Unable to remove original description");
        _descriptions.Remove(description); //TODO: valueobject convert
        //description.SetDeleted();
    }

    public void SetDescriptionValue(Description descriptionItem, Guid? languageId, string? value)
    {
        var description = GetDescription(descriptionItem);
        var newValue = value?.Trim();
        if (string.IsNullOrWhiteSpace(newValue)) throw new ArgumentNullException(nameof(value));
        if (!descriptionItem.IsOriginal && _descriptions.Any(q => q.LanguageId == (languageId ?? descriptionItem.LanguageId) && q.Value == (newValue ?? descriptionItem.Value)))
            throw new Exception("Descriptoin item with with same value is already exists");
        if (languageId.HasValue) descriptionItem.SetLanguage(languageId.Value);
        if (!string.IsNullOrWhiteSpace(newValue)) descriptionItem.SetValue(newValue);
    }

    private Description GetDescription(Description description)
    {
        return _descriptions.FirstOrDefault(q => q == description) ??
            throw new Exception($"Description is not found");
    }
    #endregion

    #region Genre
    public void AddGenre(Guid genreId, decimal? proportion = null)
    {
        if (_genres.Any(q => q.GenreId == genreId)) throw new Exception("Genre is already in the list");
        var genreProportionItem = new GenreProportion(genreId, proportion);
        _genres.Add(genreProportionItem);
        if (proportion.HasValue) genreProportionItem.SetProportion(proportion);
    }

    public void SetGenreProportion(Guid genreId, decimal? value = null)
    {
        var genreProportion = GetGenreProportionItem(genreId);
        genreProportion.SetProportion(value);
    }

    public void RemoveGenre(Guid genreId)
    {
        var genreProportion = GetGenreProportionItem(genreId);
        _genres.Remove(genreProportion);
    }

    private GenreProportion GetGenreProportionItem(Guid genreId)
    {
        return _genres.FirstOrDefault(q => q.GenreId == genreId) ??
            throw new Exception($"Genre proportion is not found");
    }
    /*
    private GenreProportion GetGenreProportionItem(GenreProportion genreProportionItem)
    {
        return _genres.FirstOrDefault(q => q == genreProportionItem) ??
            throw new Exception($"Genre proportion is not found");
    }
    */
    #endregion

    #region RelatedContent

    public void AddRelatedContent(string objectId, MediaInfoType type, string contentType)
    {
        if (string.IsNullOrWhiteSpace(objectId)) throw new ArgumentNullException(nameof(objectId));
        CheckIfCanAddOrEdit(objectId, type);
        _relatedContent.Add(new MediaInfo(objectId, type, contentType));
    }

    public void EditRelatedContent(MediaInfo relatedContentItem, string? objectId, MediaInfoType? type, string? contentType)
    {
        var relatedContent = GetRelatedContent(relatedContentItem);
        CheckIfCanAddOrEdit(objectId, type);
        if (!string.IsNullOrWhiteSpace(objectId)) relatedContent.SetObjectId(objectId);
        if (type.HasValue) relatedContent.SetType(type.Value);
        if (!string.IsNullOrWhiteSpace(contentType)) relatedContent.SetContentType(contentType);
    }

    public void RemoveRelatedContent(MediaInfo relatedContentItem)
    {
        var relatedContent = GetRelatedContent(relatedContentItem);
        _relatedContent.Remove(relatedContent); //TODO: valueobject convert
    }

    private void CheckIfCanAddOrEdit(string? objectId, MediaInfoType? type)
    {
        if (string.IsNullOrWhiteSpace(objectId) && !type.HasValue) return;
        var query = _relatedContent.AsQueryable();
        if (!string.IsNullOrWhiteSpace(objectId)) query = query.Where(q => q.ObjectId == objectId);
        if (type.HasValue) query = query.Where(q => q.Type == type);
        if (query.Any()) throw new Exception("Related content with same url and same type is already exists");
    }


    private MediaInfo GetRelatedContent(MediaInfo relatedContentItem)
    {
        return _relatedContent.FirstOrDefault(q => q == relatedContentItem) ??
            throw new Exception($"Related content is not found");
    }
    #endregion


    public void SetAnnouncementDate(DateTimeOffset? value)
    {
        AnnouncementDate = value;
    }

    public void SetEstimatedReleaseDate(DateTimeOffset? value)
    {
        //if (value == default) throw new ArgumentNullException(nameof(value));
        if (value.HasValue && value.Value == default) throw new ArgumentOutOfRangeException(nameof(value));
        if (AnnouncementDate.HasValue && value <= AnnouncementDate.Value)
            throw new Exception("Estimated release date can not be less or equal to announcement date");
        EstimatedReleaseDate = value;
    }
    public void SetReleaseDate(DateTimeOffset? value)
    {
        if (value.HasValue && value.Value == default) throw new ArgumentOutOfRangeException(nameof(value));
        if (AnnouncementDate.HasValue && value <= AnnouncementDate.Value)
            throw new Exception("Release date can not be less or equal to announcement date");
        ReleaseDate = value;
    }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        foreach (var name in _names)
        {
            yield return name;
        }
        yield return Preview;
        foreach (var descriptions in _descriptions)
        {
            yield return descriptions;
        }
        foreach (var genre in _genres)
        {
            yield return genre;
        }
        foreach (var relatedContent in _relatedContent)
        {
            yield return relatedContent;
        }
        yield return AnnouncementDate;
        yield return EstimatedReleaseDate;
        yield return ReleaseDate;
    }
}
