using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using FluentValidation;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;

namespace NzbDrone.Core.Indexers.Definitions
{
    public class Shizaproject : TorrentIndexerBase<ShizaprojectSettings>
    {
        public override string Name => "ShizaProject";
        public override string[] IndexerUrls => new string[] { "https://shiza-project.com/" };
        public override string Description => "Shizaproject is russian anime voiceover group and eponymous anime tracker.";
        public override string Language => "ru-RU";
        public override Encoding Encoding => Encoding.UTF8;
        public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
        public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
        public override IndexerCapabilities Capabilities => SetCapabilities();

        public Shizaproject(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
            : base(httpClient, eventAggregator, indexerStatusService, configService, logger)
        {
        }

        public override IIndexerRequestGenerator GetRequestGenerator()
        {
            return new ShizaprojectRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
        }

        public override IParseIndexerResponse GetParser()
        {
            return new ShizaprojectParser(Settings, Capabilities.Categories);
        }

        private IndexerCapabilities SetCapabilities()
        {
            var caps = new IndexerCapabilities
            {
                TvSearchParams = new List<TvSearchParam>
                                   {
                                       TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
                                   },
                MovieSearchParams = new List<MovieSearchParam>
                                   {
                                       MovieSearchParam.Q
                                   }
            };
            caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV");
            caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "TV_SPECIAL");
            caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVAnime, "ONA");
            caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "OVA");
            caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "MOVIE");
            caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Movies, "SHORT_MOVIE");
            return caps;
        }
    }

    public class ShizaprojectRequestGenerator : IIndexerRequestGenerator
    {
        public ShizaprojectSettings Settings { get; set; }
        public IndexerCapabilities Capabilities { get; set; }

        public ShizaprojectRequestGenerator()
        {
        }

        private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
        {
            var variables = new
            {
                // Number of fetched releases (required parameter) TODO: consider adding pagination
                first = 50,

                // Remove season and episode info from search term cause it breaks search
                query = Regex.Replace(term, @"(?:[SsEe]?\d{1,4}){1,2}$", "").TrimEnd()
            };

            var query = @"
            query fetchReleases($first: Int, $query: String) {
                releases(first: $first, query: $query) {
                    edges {
                        node {
                            name
                            type
                            originalName
                            alternativeNames
                            publishedAt
                            slug
                            torrents {
                                downloaded
                                seeders
                                leechers
                                size
                                magnetUri
                                updatedAt
                                file {
                                    url
                                }
                                videoQualities
                            }
                        }
                    }
                }
            }";

            var queryCollection = new NameValueCollection
            {
                { "query", query.Replace('\n', ' ').Trim() },
                { "variables", Newtonsoft.Json.JsonConvert.SerializeObject(variables) }
            };

            var requestUrl = string.Format("{0}/graphql?", Settings.BaseUrl.TrimEnd('/')) + queryCollection.GetQueryString();

            var request = new IndexerRequest(requestUrl, HttpAccept.Json);
            yield return request;
        }

        public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
        {
            var pageableRequests = new IndexerPageableRequestChain();

            pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));

            return pageableRequests;
        }

        public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
        {
            var pageableRequests = new IndexerPageableRequestChain();

            pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));

            return pageableRequests;
        }

        public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
        {
            var pageableRequests = new IndexerPageableRequestChain();

            pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));

            return pageableRequests;
        }

        // Shizaproject doesn't support music, but this function required by interface
        public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
        {
            return new IndexerPageableRequestChain();
        }

        // Shizaproject doesn't support books, but this function required by interface
        public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
        {
            return new IndexerPageableRequestChain();
        }

        public Func<IDictionary<string, string>> GetCookies { get; set; }
        public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
    }

    public class ShizaprojectParser : IParseIndexerResponse
    {
        private readonly ShizaprojectSettings _settings;
        private readonly IndexerCapabilitiesCategories _categories;

        public ShizaprojectParser(ShizaprojectSettings settings, IndexerCapabilitiesCategories categories)
        {
            _settings = settings;
            _categories = categories;
        }

        private string composeTitle(ShizaprojectNode n, ShizaprojectTorrent tr)
        {
            var title = string.Format("{0} / {1}", n.Name, n.OriginalName);
            foreach (var tl in n.AlternativeNames)
            {
                title += " / " + tl;
            }

            title += " [";
            foreach (var q in tr.VideoQualities)
            {
                title += " " + q;
            }

            title += " ]";
            return title;
        }

        private DateTime getActualPublishDate(ShizaprojectNode n, ShizaprojectTorrent t)
        {
            if (n.PublishedAt == null)
            {
                return t.UpdatedAt;
            }
            else
            {
                return (t.UpdatedAt > n.PublishedAt) ? t.UpdatedAt : n.PublishedAt.Value;
            }
        }

        private string getResolution(string[] qualities)
        {
            var resPrefix = "RESOLUTION_";
            var res = Array.Find(qualities, s => s.StartsWith(resPrefix));
            return res != null ? res.Replace(resPrefix, "") : "Unknown";
        }

        public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
        {
            var torrentInfos = new List<ReleaseInfo>();
            var response = JsonConvert.DeserializeObject<ShizaprojectReleasesResponse>(indexerResponse.Content);

            if (response.Errors != null && response.Errors.Length > 0)
            {
                var message = "Errors:\n";
                foreach (var e in response.Errors)
                {
                    message += string.Format("{0} - {1}\n", e.Extensions.Code, e.Message);
                }

                throw new IndexerException(indexerResponse, message);
            }

            foreach (var e in response.Data.Releases.Edges)
            {
                foreach (var tr in e.Node.Torrents)
                {
                    var torrentInfo = new TorrentInfo
                    {
                        Title = composeTitle(e.Node, tr),
                        InfoUrl = string.Format("{0}/releases/{1}/", _settings.BaseUrl.TrimEnd('/'), e.Node.Slug),
                        DownloadVolumeFactor = 0,
                        UploadVolumeFactor = 1,
                        Seeders = tr.Seeders,
                        Peers = tr.Leechers + tr.Seeders,
                        Grabs = tr.Downloaded,
                        Categories = _categories.MapTrackerCatDescToNewznab(e.Node.Type),
                        PublishDate = getActualPublishDate(e.Node, tr),
                        Guid = tr.File.Url,
                        DownloadUrl = tr.File.Url,
                        MagnetUrl = tr.MagnetUri,
                        Size = tr.Size,
                        Resolution = getResolution(tr.VideoQualities)
                    };

                    torrentInfos.Add(torrentInfo);
                }
            }

            return torrentInfos.ToArray();
        }

        public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
    }

    public class ShizaprojectSettingsValidator : AbstractValidator<ShizaprojectSettings>
    {
        public ShizaprojectSettingsValidator()
        {
        }
    }

    public class ShizaprojectSettings : IIndexerSettings
    {
        private static readonly ShizaprojectSettingsValidator Validator = new ShizaprojectSettingsValidator();

        [FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
        public string BaseUrl { get; set; }

        [FieldDefinition(2)]
        public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();

        public NzbDroneValidationResult Validate()
        {
            return new NzbDroneValidationResult(Validator.Validate(this));
        }
    }

    public class ShizaprojectReleasesResponse
    {
        public ShizaprojectData Data { get; set; }
        public ShizaprojectError[] Errors { get; set; }
    }

    public class ShizaprojectError
    {
        public ShizaprojectErrorExtensions Extensions { get; set; }
        public string Message { get; set; }
    }

    public class ShizaprojectErrorExtensions
    {
        public string Code { get; set; }
    }

    public class ShizaprojectData
    {
        public ShizaprojectReleases Releases { get; set; }
    }

    public class ShizaprojectReleases
    {
        public ShizaprojectEdge[] Edges { get; set; }
    }

    public class ShizaprojectEdge
    {
        public ShizaprojectNode Node { get; set; }
    }

    public class ShizaprojectNode
    {
        public string Name { get; set; }
        public string OriginalName { get; set; }
        public string[] AlternativeNames { get; set; }
        public DateTime? PublishedAt { get; set; }
        public string Slug { get; set; }
        public ShizaprojectTorrent[] Torrents { get; set; }
        public string Type { get; set; }
    }

    public class ShizaprojectFile
    {
        public string Url { get; set; }
    }

    public class ShizaprojectTorrent
    {
        public int Downloaded { get; set; }
        public int Seeders { get; set; }
        public int Leechers { get; set; }
        public long Size { get; set; }
        public string MagnetUri { get; set; }
        public DateTime UpdatedAt { get; set; }
        public string[] VideoQualities { get; set; }
        public ShizaprojectFile File { get; set; }
    }
}
