using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using NLog;
using Ombi.Api;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Notification;
using Ombi.Store;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;

namespace Ombi.UI.Modules
{
    public class IssuesModule : BaseAuthModule
    {
        public IssuesModule(ISettingsService<PlexRequestSettings> pr, IIssueService issueService, IRequestService request, INotificationService n, ISecurityExtensions security) : base("issues", pr, security)
        {
            IssuesService = issueService;
            RequestService = request;
            NotificationService = n;

            Get["/"] = x => Index();

            Get["/{id}", true] = async (x, ct) => await Details(x.id);

            Post["/issue", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);

            Get["/pending", true] = async (x, ct) => await GetIssues(IssueStatus.PendingIssue);
            Get["/resolved", true] = async (x, ct) => await GetIssues(IssueStatus.ResolvedIssue);

            Post["/remove", true] = async (x, ct) => await RemoveIssue((int)Request.Form.issueId);
            Post["/resolvedUpdate", true] = async (x, ct) => await ChangeStatus((int)Request.Form.issueId, IssueStatus.ResolvedIssue);

            Post["/clear", true] = async (x, ct) => await ClearIssue((int)Request.Form.issueId, (IssueState)(int)Request.Form.issue);

            Get["/issuecount", true] = async (x, ct) => await IssueCount();
            Get["/tabCount", true] = async (x, ct) => await TabCount();

            Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea);

            Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null);

            Post["/nonrequestissuecomment", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, IssueState.Other, (string)Request.Form.commentArea);


            Post["/addnote", true] = async (x, ct) => await AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea, (IssueState)(int)Request.Form.issue);
        }

        private IIssueService IssuesService { get; }
        private IRequestService RequestService { get; }
        private INotificationService NotificationService { get; }
        private static Logger Log = LogManager.GetCurrentClassLogger();

        public Negotiator Index()
        {
            return View["Index"];
        }

        private async Task<Response> GetIssues(IssueStatus status)
        {
            var issues = await IssuesService.GetAllAsync();
            issues = await FilterIssuesAsync(issues, status == IssueStatus.ResolvedIssue);

            var issuesModels = issues as IssuesModel[] ?? issues.Where(x => x.IssueStatus == status).ToArray();
            var viewModel = new List<IssuesViewModel>();

            foreach (var i in issuesModels)
            {
                var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests)
            };

                // Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues
                var state = i.Issues.Select(x => x.Issue).ToArray();
                var issueState = string.Empty;
                for (var j = 0; j < state.Length; j++)
                {
                    var word = state[j].ToString().ToCamelCaseWords();
                    if (j != state.Length - 1)
                    {
                        issueState += $"{word}, ";
                    }
                    else
                    {
                        issueState += word;
                    }
                }
                model.Issues = issueState;

                viewModel.Add(model);
            }

            return Response.AsJson(viewModel);
        }

        public async Task<Response> IssueCount()
        {
            var issues = await IssuesService.GetAllAsync();

            var myIssues = await FilterIssuesAsync(issues);

            var count = myIssues.Count();

            return Response.AsJson(count);
        }

        public async Task<Response> TabCount()
        {
            var issues = await IssuesService.GetAllAsync();

            var myIssues = await FilterIssuesAsync(issues);

            var count = new List<object>();

            var issuesModels = myIssues as IssuesModel[] ?? myIssues.ToArray();
            var pending = issuesModels.Where(x => x.IssueStatus == IssueStatus.PendingIssue);
            var resolved = issuesModels.Where(x => x.IssueStatus == IssueStatus.ResolvedIssue);

            count.Add(new { Name = IssueStatus.PendingIssue, Count = pending.Count() });
            count.Add(new { Name = IssueStatus.ResolvedIssue, Count = resolved.Count() });

            return Response.AsJson(count);
        }

        public async Task<Negotiator> Details(int id)
        {
            var issue = await IssuesService.GetAsync(id);
            if (issue == null)
                return Index();

            issue = Order(issue);
            var m = new IssuesDetailsViewModel
            {
                Issues = issue.Issues,
                RequestId = issue.RequestId,
                Title = issue.Title,
                IssueStatus = issue.IssueStatus,
                Deleted = issue.Deleted,
                Type = issue.Type,
                ProviderId = issue.ProviderId,
                PosterUrl = issue.PosterUrl.Contains("https://image.tmdb.org/t/p/w150/") ? issue.PosterUrl : $"https://image.tmdb.org/t/p/w150/{issue.PosterUrl}",
                Id = issue.Id
            };
            return View["Details", m];
        }

        private async Task<Response> ReportRequestIssue(int requestId, IssueState issue, string comment)
        {

            var model = new IssueModel
            {
                Issue = issue,
                UserReported = Username,
                UserNote = !string.IsNullOrEmpty(comment)
                ? $"{Username} - {comment}"
                : string.Empty,
            };

            var request = await RequestService.GetAsync(requestId);

            var issueEntity = await IssuesService.GetAllAsync();
            var existingIssue = issueEntity.FirstOrDefault(x => x.RequestId == requestId);

            var notifyModel = new NotificationModel
            {
                User = Username,
                NotificationType = NotificationType.Issue,
                Title = request.Title,
                DateTime = DateTime.Now,
                Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
            };

            // An issue already exists
            if (existingIssue != null)
            {
                if (existingIssue.Issues.Any(x => x.Issue == issue))
                {
                    return
                        Response.AsJson(new JsonResponseModel
                        {
                            Result = false,
                            Message = "This issue has already been reported!"
                        });

                }
                existingIssue.Issues.Add(model);
                var result = await IssuesService.UpdateIssueAsync(existingIssue);


                await NotificationService.Publish(notifyModel);

                return Response.AsJson(result
                    ? new JsonResponseModel { Result = true }
                    : new JsonResponseModel { Result = false });
            }

            // New issue
            var issues = new IssuesModel
            {
                Title = request.Title,
                PosterUrl = request.PosterPath,
                RequestId = requestId,
                Type = request.Type,
                IssueStatus = IssueStatus.PendingIssue
            };
            issues.Issues.Add(model);

            var issueId = await IssuesService.AddIssueAsync(issues);

            request.IssueId = issueId;
            await RequestService.UpdateRequestAsync(request);

            await NotificationService.Publish(notifyModel);
            return Response.AsJson(new JsonResponseModel { Result = true });
        }

        private async Task<Response> ReportNonRequestIssue(int providerId, string type, IssueState issue, string comment)
        {
            var currentIssues = await IssuesService.GetAllAsync();
            var notifyModel = new NotificationModel
            {
                User = Username,
                NotificationType = NotificationType.Issue,
                DateTime = DateTime.Now,
                Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
            };
            var model = new IssueModel
            {
                Issue = issue,
                UserReported = Username,
                UserNote = !string.IsNullOrEmpty(comment)
                ? $"{Username} - {comment}"
                : string.Empty,
            };

            var existing = currentIssues.FirstOrDefault(x => x.ProviderId == providerId && !x.Deleted && x.IssueStatus == IssueStatus.PendingIssue);
            if (existing != null)
            {
                existing.Issues.Add(model);
                await IssuesService.UpdateIssueAsync(existing);
                return Response.AsJson(new JsonResponseModel { Result = true });
            }

            if (type == "movie")
            {
                var movieApi = new TheMovieDbApi();

                var result = await movieApi.GetMovieInformation(providerId);
                if (result != null)
                {
                    notifyModel.Title = result.Title;
                    // New issue
                    var issues = new IssuesModel
                    {
                        Title = result.Title,
                        PosterUrl = "https://image.tmdb.org/t/p/w150/" + result.PosterPath,
                        ProviderId = providerId,
                        Type = RequestType.Movie,
                        IssueStatus = IssueStatus.PendingIssue
                    };
                    issues.Issues.Add(model);

                    var issueId = await IssuesService.AddIssueAsync(issues);

                    await NotificationService.Publish(notifyModel);
                    return Response.AsJson(new JsonResponseModel { Result = true });
                }
            }

            if (type == "tv")
            {
                var tv = new TvMazeApi();
                var result = tv.ShowLookupByTheTvDbId(providerId);
                if (result != null)
                {
                    var banner = result.image?.medium;
                    if (!string.IsNullOrEmpty(banner))
                    {
                        banner = banner.Replace("http", "https");
                    }

                    notifyModel.Title = result.name;
                    // New issue
                    var issues = new IssuesModel
                    {
                        Title = result.name,
                        PosterUrl = banner,
                        ProviderId = providerId,
                        Type = RequestType.TvShow,
                        IssueStatus = IssueStatus.PendingIssue
                    };
                    issues.Issues.Add(model);

                    var issueId = await IssuesService.AddIssueAsync(issues);

                    await NotificationService.Publish(notifyModel);
                    return Response.AsJson(new JsonResponseModel { Result = true });
                }
            }



            return Response.AsJson(new JsonResponseModel { Result = false, Message = "Album Reports are not supported yet!"});
        }

        /// <summary>
        /// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting.
        /// </summary>
        /// <param name="issues">The issues.</param>
        private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false)
        {
            var settings = await PlexRequestSettings.GetSettingsAsync();
            IEnumerable<IssuesModel> myIssues;

            // Is the user an Admin? If so show everything
            if (IsAdmin)
            {
                var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
                myIssues = issuesModels.Where(x => x.Deleted == false);
                if (!showResolved)
                {
                    myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
                }
            }
            else if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnIssues)) // The user is not an Admin, do we have the settings to hide them?
            {
                if (!showResolved)
                {
                    myIssues =
                        issues.Where(
                            x =>
                            x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false
                            && x.IssueStatus != IssueStatus.ResolvedIssue);
                }
                else
                {
                    myIssues =
                        issues.Where(
                            x =>
                                x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false);
                }
            }
            else // Looks like the user is not an admin and there is no settings set.
            {
                var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
                myIssues = issuesModels.Where(x => x.Deleted == false);
                if (!showResolved)
                {
                    myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
                }
            }

            return myIssues;
        }
        private async Task<Response> RemoveIssue(int issueId)
        {
            try
            {
                if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
                {
                    return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to remove an issue." });
                }

                var issue = await IssuesService.GetAsync(issueId);
                var request = await RequestService.GetAsync(issue.RequestId);
                if (request.Id > 0)
                {
                    request.IssueId = 0; // No issue;

                    var result = await RequestService.UpdateRequestAsync(request);
                    if (result)
                    {
                        await IssuesService.DeleteIssueAsync(issueId);
                    }
                }
                else
                {
                    await IssuesService.DeleteIssueAsync(issueId);
                }

                return Response.AsJson(new JsonResponseModel { Result = true });

            }
            catch (Exception e)
            {
                Log.Error(e);
                return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."});
            }

        }

        private async Task<Negotiator> ChangeStatus(int issueId, IssueStatus status)
        {
            try
            {
                if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
                {
                    return View["Index"];
                }


                var issue = await IssuesService.GetAsync(issueId);
                issue.IssueStatus = status;
                var result = await IssuesService.UpdateIssueAsync(issue);
                return result ? await Details(issueId) : View["Index"];
            }
            catch (Exception e)
            {
                Log.Error(e);
                return View["Index"];
            }

        }


        private async Task<Negotiator> ClearIssue(int issueId, IssueState state)
        {
            if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
            {
                return View["Index"];
            }

            var issue = await IssuesService.GetAsync(issueId);

            var toRemove = issue.Issues.FirstOrDefault(x => x.Issue == state);
            issue.Issues.Remove(toRemove);

            var result = await IssuesService.UpdateIssueAsync(issue);

            return result ? await Details(issueId) : View["Index"];
        }

        private async Task<Response> AddNote(int requestId, string noteArea, IssueState state)
        {
            if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
            {
                return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to add a note." });
            }

            var issue = await IssuesService.GetAsync(requestId);
            if (issue == null)
            {
                return Response.AsJson(new JsonResponseModel { Result = false, Message = "Issue does not exist to add a note!" });
            }
            var toAddNote = issue.Issues.FirstOrDefault(x => x.Issue == state);

            if (toAddNote != null)
            {
                issue.Issues.Remove(toAddNote);
                toAddNote.AdminNote = noteArea;
                issue.Issues.Add(toAddNote);
            }

            var result = await IssuesService.UpdateIssueAsync(issue);
            return Response.AsJson(result
                                       ? new JsonResponseModel { Result = true }
                                       : new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });
        }

        /// <summary>
        /// Orders the issues descending by the <see cref="IssueState"/>.
        /// </summary>
        /// <param name="issues">The issues.</param>
        /// <returns></returns>
        private IssuesModel Order(IssuesModel issues)
        {
            issues.Issues = issues.Issues.OrderByDescending(x => x.Issue).ToList();
            return issues;
        }
    }
}