using MangaReader.Core.Search;
using MangaReader.Core.Sources.MangaDex.Api;
using System.Text.RegularExpressions;

namespace MangaReader.Core.Sources.MangaDex.Search;

public partial class MangaDexSearchProvider(IMangaDexClient mangaDexClient) : IMangaSearchProvider
{
    [GeneratedRegex(@"[^a-z0-9\s-]")]
    private static partial Regex InvalidSlugCharactersRegex();

    [GeneratedRegex(@"\s+")]
    private static partial Regex WhitespaceRegex();

    public string SourceId => "MangaDex";

    public async Task<MangaSearchResult[]> SearchAsync(string keyword, CancellationToken cancellationToken)
    {
        MangaDexResponse? response = await mangaDexClient.SearchMangaByTitleAsync(keyword, cancellationToken);

        if (response == null || (response is not MangaDexCollectionResponse collectionResponse))
            return [];

        MangaEntity[] mangaEntities = [.. collectionResponse.Data.Where(entity => entity is MangaEntity).Cast<MangaEntity>()];

        if (mangaEntities.Length == 0)
            return [];

        Dictionary<Guid, List<CoverArtEntity>> mangaCoverArtMap = await GetCoverArtFileNamesAsync(mangaEntities, cancellationToken);

        List<MangaSearchResult> mangaSearchResults = [];
        Dictionary<Guid, MangaSearchResult> thing = [];

        foreach (MangaEntity mangaEntity in mangaEntities)
        {
            CoverArtEntity[] coverArtEntites = [.. mangaCoverArtMap[mangaEntity.Id]];

            MangaSearchResult? mangaSearchResult = GetMangaSearchResult(mangaEntity, coverArtEntites);

            if (mangaSearchResult == null)
                continue;

            mangaSearchResults.Add(mangaSearchResult);
        }

        if (thing.Count > 0)
        {
            Guid[] mangaGuids = thing.Select(x => x.Key).ToArray();
            var reults = await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken);
            //var reults = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken);
        }

        return [.. mangaSearchResults];
    }

    private static MangaSearchResult? GetMangaSearchResult(MangaEntity mangaEntity, CoverArtEntity[] coverArtEntites)
    {
        MangaAttributes? mangaAttributes = mangaEntity.Attributes;

        if (mangaAttributes == null)
            return null;

        string title = GetTitle(mangaAttributes);
        string slug = GenerateSlug(title);

        MangaSearchResult mangaSearchResult = new()
        {
            Title = title,
            Description = GetDescription(mangaAttributes),
            Genres = GetGenres(mangaAttributes),
            Url = $"https://mangadex.org/title/{mangaEntity.Id}/{slug}",
            Thumbnail = GetThumbnail(mangaEntity, coverArtEntites)
        };

        return mangaSearchResult;
    }

    private static string GetTitle(MangaAttributes attributes)
    {
        var alternateTitle = attributes.AltTitles.Where(x => x.ContainsKey("en")).FirstOrDefault();

        if (alternateTitle?.Count > 0)
            return alternateTitle["en"];

        if (attributes.Title.TryGetValue("en", out string? title))
            return title;

        if (attributes.Title.Count > 0)
            return attributes.Title.ElementAt(0).Value;

        return string.Empty;
    }

    private static string GetDescription(MangaAttributes attributes)
    {
        if (attributes.Description.TryGetValue("en", out string? description))
            return description;

        if (attributes.Description.Count > 0)
            return attributes.Description.ElementAt(0).Value;

        return string.Empty;
    }

    private static string[] GetGenres(MangaAttributes attributes)
    {
        if (attributes.Tags == null || attributes.Tags.Count == 0)
            return [];

        List<string> tags = [];

        foreach (TagEntity tagEntity in attributes.Tags)
        {
            if (tagEntity.Attributes == null)
                continue;

            if (tagEntity.Attributes.Name == null || tagEntity.Attributes.Name.Count == 0)
                continue;

            tags.Add(tagEntity.Attributes.Name.FirstOrDefault().Value);
        }

        return [.. tags];
    }

    public static string GenerateSlug(string title)
    {
        // title.ToLowerInvariant().Normalize(NormalizationForm.FormD);

        title = title.ToLowerInvariant();
        //title = InvalidSlugCharactersRegex().Replace(title, ""); // remove invalid chars
        title = InvalidSlugCharactersRegex().Replace(title, "-"); // replace invalid chars with dash
        title = WhitespaceRegex().Replace(title, "-");         // replace spaces with dash

        return title.Trim('-');
    }

    private static string? GetThumbnail(MangaDexEntity mangaDexEntity, CoverArtEntity[] coverArtEntites)
    {
        string? fileName = GetCoverArtFileNameFromMangaEntity(mangaDexEntity)
            ?? GetCoverArtFileNameFromCoverArtEntities(coverArtEntites);

        if (string.IsNullOrWhiteSpace(fileName))
            return null;

        return $"https://mangadex.org/covers/{mangaDexEntity.Id}/{fileName}";
    }

    private static string? GetCoverArtFileNameFromMangaEntity(MangaDexEntity mangaDexEntity)
    {
        CoverArtEntity? coverArtEntity = (CoverArtEntity?)mangaDexEntity.Relationships.FirstOrDefault(entity =>
            entity is CoverArtEntity);

        if (coverArtEntity == null || string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName))
            return null;

        return coverArtEntity.Attributes?.FileName;
    }

    private static string? GetCoverArtFileNameFromCoverArtEntities(CoverArtEntity[] coverArtEntites)
    {
        return coverArtEntites.Where(coverArtEntity =>
            string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName) == false).FirstOrDefault()?.Attributes!.FileName;
    }

    private async Task<Dictionary<Guid, List<CoverArtEntity>>> GetCoverArtFileNamesAsync(MangaEntity[] mangaEntities, CancellationToken cancellationToken)
    {
        Guid[] mangaGuids = [.. mangaEntities.Select(entity => entity.Id)];

        return await GetCoverArtFileNamesAsync(mangaGuids, cancellationToken);
    }

    private async Task<Dictionary<Guid, List<CoverArtEntity>>> GetCoverArtFileNamesAsync(Guid[] mangaGuids, CancellationToken cancellationToken)
    {
        Dictionary<Guid, List<CoverArtEntity>> result = [];

        foreach (Guid mangaGuid in  mangaGuids)
        {
            result.Add(mangaGuid, []);
        }

        MangaDexResponse? response = await mangaDexClient.GetCoverArtAsync(mangaGuids, cancellationToken);

        if (response == null || (response is not MangaDexCollectionResponse collectionResponse))
            return result;

        CoverArtEntity[] coverArtEntities = [.. collectionResponse.Data.Where(entity => entity is CoverArtEntity).Cast<CoverArtEntity>()];

        if (coverArtEntities.Length == 0)
            return result;

        CoverArtEntity[] orderedCoverArtEntities = [.. coverArtEntities.OrderBy(x => x.Attributes?.Volume)];

        foreach (var coverArtEntity in orderedCoverArtEntities)
        {
            if (coverArtEntity.Attributes == null)
                continue;

            MangaEntity? mangaEntity = (MangaEntity?)coverArtEntity.Relationships.FirstOrDefault(relationship => relationship is MangaEntity);

            if (mangaEntity == null)
                continue;

            if (result.ContainsKey(mangaEntity.Id) == false)
                continue;

            result[mangaEntity.Id].Add(coverArtEntity);
        }

        return result;
    }
}