using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;

namespace NzbDrone.Core.Notifications.Plex.Server
{
    public interface IPlexServerProxy
    {
        List<PlexSection> GetMovieSections(PlexServerSettings settings);
        void Update(int sectionId, PlexServerSettings settings);
        void UpdateMovie(int metadataId, PlexServerSettings settings);
        string Version(PlexServerSettings settings);
        List<PlexPreference> Preferences(PlexServerSettings settings);
        int? GetMetadataId(int sectionId, string imdbId, string language, PlexServerSettings settings);
    }

    public class PlexServerProxy : IPlexServerProxy
    {
        private readonly IHttpClient _httpClient;
        private readonly IConfigService _configService;
        private readonly Logger _logger;

        public PlexServerProxy(IHttpClient httpClient, IConfigService configService, Logger logger)
        {
            _httpClient = httpClient;
            _configService = configService;
            _logger = logger;
        }

        public List<PlexSection> GetMovieSections(PlexServerSettings settings)
        {
            var request = BuildRequest("library/sections", HttpMethod.GET, settings);
            var response = ProcessRequest(request);

            CheckForError(response);

            if (response.Contains("_children"))
            {
                return Json.Deserialize<PlexMediaContainerLegacy>(response)
                    .Sections
                    .Where(d => d.Type == "movie")
                    .Select(s => new PlexSection
                    {
                        Id = s.Id,
                        Language = s.Language,
                        Locations = s.Locations,
                        Type = s.Type
                    })
                    .ToList();
            }

            return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response)
                       .MediaContainer
                       .Sections
                       .Where(d => d.Type == "movie")
                       .ToList();
        }

        public void Update(int sectionId, PlexServerSettings settings)
        {
            var resource = $"library/sections/{sectionId}/refresh";
            var request = BuildRequest(resource, HttpMethod.GET, settings);
            var response = ProcessRequest(request);

            CheckForError(response);
        }

        public void UpdateMovie(int metadataId, PlexServerSettings settings)
        {
            var resource = $"library/metadata/{metadataId}/refresh";
            var request = BuildRequest(resource, HttpMethod.PUT, settings);
            var response = ProcessRequest(request);

            CheckForError(response);
        }

        public string Version(PlexServerSettings settings)
        {
            var request = BuildRequest("identity", HttpMethod.GET, settings);
            var response = ProcessRequest(request);

            CheckForError(response);

            if (response.Contains("_children"))
            {
                return Json.Deserialize<PlexIdentity>(response)
                           .Version;
            }

            return Json.Deserialize<PlexResponse<PlexIdentity>>(response)
                       .MediaContainer
                       .Version;
        }

        public List<PlexPreference> Preferences(PlexServerSettings settings)
        {
            var request = BuildRequest(":/prefs", HttpMethod.GET, settings);
            var response = ProcessRequest(request);

            CheckForError(response);

            if (response.Contains("_children"))
            {
                return Json.Deserialize<PlexPreferencesLegacy>(response)
                           .Preferences;
            }

            return Json.Deserialize<PlexResponse<PlexPreferences>>(response)
                       .MediaContainer
                       .Preferences;
        }

        public int? GetMetadataId(int sectionId, string imdbId, string language, PlexServerSettings settings)
        {
            var guid = $"com.plexapp.agents.imdb://{imdbId}?lang={language}";
            var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}";
            var request = BuildRequest(resource, HttpMethod.GET, settings);
            var response = ProcessRequest(request);

            CheckForError(response);

            List<PlexSectionItem> items;

            if (response.Contains("_children"))
            {
                items = Json.Deserialize<PlexSectionResponseLegacy>(response)
                            .Items;
            }
            else
            {
                items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response)
                            .MediaContainer
                            .Items;
            }

            if (items == null || items.Empty())
            {
                return null;
            }

            return items.First().Id;
        }

        private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings)
        {
            var scheme = settings.UseSsl ? "https" : "http";

            var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
                                 .Accept(HttpAccept.Json)
                                 .AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
                                 .AddQueryParam("X-Plex-Product", BuildInfo.AppName)
                                 .AddQueryParam("X-Plex-Platform", "Windows")
                                 .AddQueryParam("X-Plex-Platform-Version", "7")
                                 .AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName)
                                 .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString());

            if (settings.AuthToken.IsNotNullOrWhiteSpace())
            {
                requestBuilder.AddQueryParam("X-Plex-Token", settings.AuthToken);
            }

            requestBuilder.ResourceUrl = resource;
            requestBuilder.Method = method;

            return requestBuilder;
        }

        private string ProcessRequest(HttpRequestBuilder requestBuilder)
        {
            var httpRequest = requestBuilder.Build();

            HttpResponse response;

            _logger.Debug("Url: {0}", httpRequest.Url);

            try
            {
                response = _httpClient.Execute(httpRequest);
            }
            catch (HttpException ex)
            {
                if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    throw new PlexAuthenticationException("Unauthorized - AuthToken is invalid");
                }

                throw new PlexException("Unable to connect to Plex Media Server. Status Code: {0}", ex.Response.StatusCode);
            }
            catch (WebException ex)
            {
                throw new PlexException("Unable to connect to Plex Media Server", ex);
            }

            return response.Content;
        }

        private void CheckForError(string response)
        {
            _logger.Trace("Checking for error");

            if (response.IsNullOrWhiteSpace())
            {
                _logger.Trace("No response body returned, no error detected");
                return;
            }

            var error = response.Contains("_children") ?
                        Json.Deserialize<PlexError>(response) :
                        Json.Deserialize<PlexResponse<PlexError>>(response).MediaContainer;

            if (error != null && !error.Error.IsNullOrWhiteSpace())
            {
                throw new PlexException(error.Error);
            }

            _logger.Trace("No error detected");
        }
    }
}
