using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace MediaBrowser.Controller.Providers.TV
{
    /// 
    /// Class RemoteEpisodeProvider
    /// 
    class RemoteEpisodeProvider : BaseMetadataProvider
    {
        /// 
        /// Gets the HTTP client.
        /// 
        /// The HTTP client.
        protected IHttpClient HttpClient { get; private set; }
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The HTTP client.
        /// The log manager.
        /// The configuration manager.
        public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager)
            : base(logManager, configurationManager)
        {
            HttpClient = httpClient;
        }
        /// 
        /// The episode query
        /// 
        private const string episodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/default/{2}/{3}/{4}.xml";
        /// 
        /// The abs episode query
        /// 
        private const string absEpisodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/absolute/{2}/{3}.xml";
        /// 
        /// Supportses the specified item.
        /// 
        /// The item.
        /// true if XXXX, false otherwise
        public override bool Supports(BaseItem item)
        {
            return item is Episode;
        }
        /// 
        /// Gets the priority.
        /// 
        /// The priority.
        public override MetadataProviderPriority Priority
        {
            get { return MetadataProviderPriority.Second; }
        }
        /// 
        /// Gets a value indicating whether [requires internet].
        /// 
        /// true if [requires internet]; otherwise, false.
        public override bool RequiresInternet
        {
            get { return true; }
        }
        protected override bool RefreshOnFileSystemStampChange
        {
            get
            {
                return true;
            }
        }
        /// 
        /// Needses the refresh internal.
        /// 
        /// The item.
        /// The provider info.
        /// true if XXXX, false otherwise
        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
        {
            bool fetch = false;
            var episode = (Episode)item;
            var downloadDate = providerInfo.LastRefreshed;
            if (ConfigurationManager.Configuration.MetadataRefreshDays == -1 && downloadDate != DateTime.MinValue)
            {
                return false;
            }
            if (!item.DontFetchMeta && !HasLocalMeta(episode))
            {
                fetch = ConfigurationManager.Configuration.MetadataRefreshDays != -1 &&
                    DateTime.Today.Subtract(downloadDate).TotalDays > ConfigurationManager.Configuration.MetadataRefreshDays;
            }
            return fetch;
        }
        /// 
        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
        /// 
        /// The item.
        /// if set to true [force].
        /// Task{System.Boolean}.
        protected override async Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            var episode = (Episode)item;
            if (!item.DontFetchMeta && !HasLocalMeta(episode))
            {
                var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
                if (seriesId != null)
                {
                    await FetchEpisodeData(episode, seriesId, cancellationToken).ConfigureAwait(false);
                    SetLastRefreshed(item, DateTime.UtcNow);
                    return true;
                }
                Logger.Info("Episode provider cannot determine Series Id for " + item.Path);
                return false;
            }
            Logger.Info("Episode provider not fetching because local meta exists or requested to ignore: " + item.Name);
            return false;
        }
        /// 
        /// Fetches the episode data.
        /// 
        /// The episode.
        /// The series id.
        /// Task{System.Boolean}.
        private async Task FetchEpisodeData(Episode episode, string seriesId, CancellationToken cancellationToken)
        {
            string name = episode.Name;
            string location = episode.Path;
            Logger.Debug("TvDbProvider: Fetching episode data for: " + name);
            string epNum = TVUtils.EpisodeNumberFromFile(location, episode.Season != null);
            if (epNum == null)
            {
                Logger.Warn("TvDbProvider: Could not determine episode number for: " + episode.Path);
                return false;
            }
            var episodeNumber = Int32.Parse(epNum);
            episode.IndexNumber = episodeNumber;
            var usingAbsoluteData = false;
            if (string.IsNullOrEmpty(seriesId)) return false;
            var seasonNumber = "";
            if (episode.Parent is Season)
            {
                seasonNumber = episode.Parent.IndexNumber.ToString();
            }
            if (string.IsNullOrEmpty(seasonNumber))
                seasonNumber = TVUtils.SeasonNumberFromEpisodeFile(location); // try and extract the season number from the file name for S1E1, 1x04 etc.
            if (!string.IsNullOrEmpty(seasonNumber))
            {
                seasonNumber = seasonNumber.TrimStart('0');
                if (string.IsNullOrEmpty(seasonNumber))
                {
                    seasonNumber = "0"; // Specials
                }
                var url = string.Format(episodeQuery, TVUtils.TVDBApiKey, seriesId, seasonNumber, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
                var doc = new XmlDocument();
                try
                {
                    using (var result = await HttpClient.Get(url, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false))
                    {
                        doc.Load(result);
                    }
                }
                catch (HttpException)
                {
                }
                //episode does not exist under this season, try absolute numbering.
                //still assuming it's numbered as 1x01
                //this is basicly just for anime.
                if (!doc.HasChildNodes && Int32.Parse(seasonNumber) == 1)
                {
                    url = string.Format(absEpisodeQuery, TVUtils.TVDBApiKey, seriesId, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
                    try
                    {
                        using (var result = await HttpClient.Get(url, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false))
                        {
                            if (result != null) doc.Load(result);
                            usingAbsoluteData = true;
                        }
                    }
                    catch (HttpException)
                    {
                    }
                }
                if (doc.HasChildNodes)
                {
                    var p = doc.SafeGetString("//filename");
                    if (p != null)
                    {
                        if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
                        try
                        {
                            episode.PrimaryImagePath = await Kernel.Instance.ProviderManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken);
                        }
                        catch (HttpException)
                        {
                        }
                        catch (IOException)
                        {
                        }
                    }
                    episode.Overview = doc.SafeGetString("//Overview");
                    if (usingAbsoluteData)
                        episode.IndexNumber = doc.SafeGetInt32("//absolute_number", -1);
                    if (episode.IndexNumber < 0)
                        episode.IndexNumber = doc.SafeGetInt32("//EpisodeNumber");
                    episode.Name = episode.IndexNumber.Value.ToString("000") + " - " + doc.SafeGetString("//EpisodeName");
                    episode.CommunityRating = doc.SafeGetSingle("//Rating", -1, 10);
                    var firstAired = doc.SafeGetString("//FirstAired");
                    DateTime airDate;
                    if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
                    {
                        episode.PremiereDate = airDate.ToUniversalTime();
                        episode.ProductionYear = airDate.Year;
                    }
                    var actors = doc.SafeGetString("//GuestStars");
                    if (actors != null)
                    {
                        episode.AddPeople(actors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = "Actor", Name = str }));
                    }
                    var directors = doc.SafeGetString("//Director");
                    if (directors != null)
                    {
                        episode.AddPeople(directors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = "Director", Name = str }));
                    }
                    var writers = doc.SafeGetString("//Writer");
                    if (writers != null)
                    {
                        episode.AddPeople(writers.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = "Writer", Name = str }));
                    }
                    if (ConfigurationManager.Configuration.SaveLocalMeta)
                    {
                        if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
                        var ms = new MemoryStream();
                        doc.Save(ms);
                        await Kernel.Instance.FileSystemManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false);
                    }
                    return true;
                }
            }
            return false;
        }
        /// 
        /// Determines whether [has local meta] [the specified episode].
        /// 
        /// The episode.
        /// true if [has local meta] [the specified episode]; otherwise, false.
        private bool HasLocalMeta(Episode episode)
        {
            return (episode.Parent.ResolveArgs.ContainsMetaFileByName(Path.GetFileNameWithoutExtension(episode.Path) + ".xml"));
        }
    }
}