using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NTDLS.Helpers;
using SixLabors.ImageSharp;
using System.Web;
using ZelWiki.Caching;
using ZelWiki.Library;
using ZelWiki.Models;
using ZelWiki.Models.DataModels;
using ZelWiki.Models.ViewModels.File;
using ZelWiki.Repository;
using static ZelWiki.Library.Images;

namespace ZelWiki.Controllers
{
    [Route("File")]
    public class FileController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
        : WikiControllerBase(signInManager, userManager)
    {
        /// <summary>
        /// 获取附加到页面的图像
        /// </summary>
        /// <param name="givenPageNavigation">The navigation link of the page.</param>
        /// <param name="givenFileNavigation">The navigation link of the file.</param>
        /// <param name="fileRevision">The revision of the the file (NOT THE PAGE REVISION).</param>
        /// <returns></returns>
        [HttpGet("Image/{givenPageNavigation}/{givenFileNavigation}/{fileRevision:int?}")]
        public ActionResult Image(string givenPageNavigation, string givenFileNavigation, int? fileRevision = null)
        {
            var pageNavigation = new NamespaceNavigation(givenPageNavigation);
            var fileNavigation = new NamespaceNavigation(givenFileNavigation);

            int givenScale = GetQueryValue("Scale", 100);

            var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [givenPageNavigation, givenFileNavigation, fileRevision, givenScale]);
            if (WikiCache.TryGet<ImageCacheItem>(cacheKey, out var cached))
            {
                return File(cached.Bytes, cached.ContentType);
            }

            var file = PageFileRepository.GetPageFileAttachmentByPageNavigationFileRevisionAndFileNavigation(pageNavigation.Canonical, fileNavigation.Canonical, fileRevision);
            if (file != null)
            {
                if (file.ContentType == "image/x-icon")
                {
                    //We do not handle the resizing of icon file. Maybe later....
                    return File(file.Data, file.ContentType);
                }

                var img = SixLabors.ImageSharp.Image.Load(new MemoryStream(file.Data));

                if (givenScale > 500)
                {
                    givenScale = 500;
                }
                if (givenScale != 100)
                {
                    int width = (int)(img.Width * (givenScale / 100.0));
                    int height = (int)(img.Height * (givenScale / 100.0));

                    //Adjusting by a ratio (and especially after applying additional scaling) may have caused one
                    //  dimension to become very small (or even negative). So here we will check the height and width
                    //  to ensure they are both at least n pixels and adjust both dimensions.
                    if (height < 16)
                    {
                        int difference = 16 - height;
                        height += difference;
                        width += difference;
                    }
                    if (width < 16)
                    {
                        int difference = 16 - width;
                        height += difference;
                        width += difference;
                    }

                    if (file.ContentType.Equals("image/gif", StringComparison.InvariantCultureIgnoreCase))
                    {
                        var resized = ResizeGifImage(file.Data, width, height);
                        return File(resized, "image/gif");
                    }
                    else
                    {
                        using var image = ResizeImage(img, width, height);
                        using var ms = new MemoryStream();
                        file.ContentType = BestEffortConvertImage(image, ms, file.ContentType);
                        var cacheItem = new ImageCacheItem(ms.ToArray(), file.ContentType);
                        WikiCache.Put(cacheKey, cacheItem);
                        return File(cacheItem.Bytes, cacheItem.ContentType);
                    }
                }
                else
                {
                    return File(file.Data, file.ContentType);
                }
            }
            else
            {
                return NotFound($"[{fileNavigation}] 无法在页面 [{pageNavigation}] 中找到.");
            }
        }

        /// <summary>
        /// Gets an image from the database, converts it to a PNG with optional scaling and returns it to the client.
        /// </summary>
        /// <param name="givenPageNavigation">The navigation link of the page.</param>
        /// <param name="givenFileNavigation">The navigation link of the file.</param>
        /// <param name="fileRevision">The revision of the the FILE (NOT THE PAGE REVISION)</param>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet("Png/{givenPageNavigation}/{givenFileNavigation}/{fileRevision:int?}")]
        public ActionResult Png(string givenPageNavigation, string givenFileNavigation, int? fileRevision = null)
        {
            SessionState.RequireViewPermission();

            var pageNavigation = new NamespaceNavigation(givenPageNavigation);
            var fileNavigation = new NamespaceNavigation(givenFileNavigation);

            int givenScale = GetQueryValue("Scale", 100);

            var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [givenPageNavigation, givenFileNavigation, fileRevision, givenScale]);
            if (WikiCache.TryGet<ImageCacheItem>(cacheKey, out var cached))
            {
                return File(cached.Bytes, cached.ContentType);
            }

            var file = PageFileRepository.GetPageFileAttachmentByPageNavigationFileRevisionAndFileNavigation(pageNavigation.Canonical, fileNavigation.Canonical, fileRevision);
            if (file != null)
            {
                var img = SixLabors.ImageSharp.Image.Load(new MemoryStream(Utility.Decompress(file.Data)));

                if (givenScale > 500)
                {
                    givenScale = 500;
                }

                if (givenScale != 100)
                {
                    int width = (int)(img.Width * (givenScale / 100.0));
                    int height = (int)(img.Height * (givenScale / 100.0));

                    //Adjusting by a ratio (and especially after applying additional scaling) may have caused one
                    //  dimension to become very small (or even negative). So here we will check the height and width
                    //  to ensure they are both at least n pixels and adjust both dimensions.
                    if (height < 16)
                    {
                        int difference = 16 - height;
                        height += difference;
                        width += difference;
                    }
                    if (width < 16)
                    {
                        int difference = 16 - width;
                        height += difference;
                        width += difference;
                    }

                    using var image = Images.ResizeImage(img, width, height);
                    using var ms = new MemoryStream();
                    image.SaveAsPng(ms);

                    var cacheItem = new ImageCacheItem(ms.ToArray(), "image/png");
                    WikiCache.Put(cacheKey, cacheItem);
                    return File(cacheItem.Bytes, cacheItem.ContentType);
                }
                else
                {
                    using var ms = new MemoryStream();
                    img.SaveAsPng(ms);

                    var cacheItem = new ImageCacheItem(ms.ToArray(), "image/png");
                    WikiCache.Put(cacheKey, cacheItem);
                    return File(cacheItem.Bytes, cacheItem.ContentType);
                }
            }
            else
            {
                return NotFound($"[{fileNavigation}] 无法在页面 [{pageNavigation}] 中找到.");
            }
        }

        /// <summary>
        /// Gets a file from the database and returns it to the client. This works even
        /// when the file or file revision is not even attached to the page anymore.
        /// <param name="givenPageNavigation">The navigation link of the page.</param>
        /// <param name="givenFileNavigation">The navigation link of the file.</param>
        /// <param name="fileRevision">The revision of the the FILE (NOT THE PAGE REVISION),</param>
        /// </summary>
        [AllowAnonymous]
        [HttpGet("Binary/{givenPageNavigation}/{givenFileNavigation}/{fileRevision:int?}")]
        public ActionResult Binary(string givenPageNavigation, string givenFileNavigation, int? fileRevision = null)
        {
            SessionState.RequireViewPermission();

            var pageNavigation = new NamespaceNavigation(givenPageNavigation);
            var fileNavigation = new NamespaceNavigation(givenFileNavigation);

            var file = PageFileRepository.GetPageFileAttachmentByPageNavigationFileRevisionAndFileNavigation(pageNavigation.Canonical, fileNavigation.Canonical, fileRevision);

            if (file != null)
            {
                return File(file.Data.ToArray(), file.ContentType);
            }
            else
            {
                HttpContext.Response.StatusCode = 404;
                return NotFound($"[{fileNavigation}] 无法在页面 [ {pageNavigation} ] 中找到.");
            }
        }

        /// <summary>
        /// Populate the upload page. Shows the attachments.
        /// </summary>
        [Authorize]
        [HttpGet("Revisions/{givenPageNavigation}/{givenFileNavigation}")]
        public ActionResult Revisions(string givenPageNavigation, string givenFileNavigation)
        {
            SessionState.RequireViewPermission();

            var pageNavigation = new NamespaceNavigation(givenPageNavigation);
            var fileNavigation = new NamespaceNavigation(givenFileNavigation);

            var model = new PageFileRevisionsViewModel()
            {
                PageNavigation = pageNavigation.Canonical,
                FileNavigation = fileNavigation.Canonical,
                Revisions = PageFileRepository.GetPageFileAttachmentRevisionsByPageAndFileNavigationPaged
                    (pageNavigation.Canonical, fileNavigation.Canonical, GetQueryValue("page", 1))
            };

            model.PaginationPageCount = (model.Revisions.FirstOrDefault()?.PaginationPageCount ?? 0);

            return View(model);
        }

        /// <summary>
        /// Populate the upload page. Shows the attachments.
        /// </summary>
        [Authorize]
        [HttpGet("PageAttachments/{givenPageNavigation}")]
        public ActionResult PageAttachments(string givenPageNavigation)
        {
            SessionState.RequireCreatePermission();

            var pageNavigation = new NamespaceNavigation(givenPageNavigation);

            var page = PageRepository.GetPageRevisionByNavigation(pageNavigation);
            if (page != null)
            {
                var pageFiles = PageFileRepository.GetPageFilesInfoByPageId(page.Id);

                return View(new FileAttachmentViewModel
                {
                    PageNavigation = page.Navigation,
                    PageRevision = page.Revision,
                    Files = pageFiles
                });
            }

            return View(new FileAttachmentViewModel
            {
                Files = new()
            });
        }

        /// <summary>
        /// Uploads a file by drag drop.
        /// </summary>
        [Authorize]
        [HttpPost("UploadDragDrop/{givenPageNavigation}")]
        public IActionResult UploadDragDrop(string givenPageNavigation, List<IFormFile> postedFiles)
        {
            SessionState.RequireCreatePermission();

            try
            {
                var pageNavigation = new NamespaceNavigation(givenPageNavigation);

                var page = PageRepository.GetPageInfoByNavigation(pageNavigation.Canonical).EnsureNotNull();

                foreach (IFormFile file in postedFiles)
                {
                    if (file != null)
                    {
                        var fileSize = file.Length;
                        if (fileSize > 0)
                        {
                            if (fileSize > GlobalConfiguration.MaxAttachmentFileSize)
                            {
                                return Json(new { message = $"无法保存文件: [{file.FileName}], 文件过大." });
                            }

                            var fileName = HttpUtility.UrlDecode(file.FileName);

                            PageFileRepository.UpsertPageFile(new PageFileAttachment()
                            {
                                Data = Utility.ConvertHttpFileToBytes(file),
                                CreatedDate = DateTime.UtcNow,
                                PageId = page.Id,
                                Name = fileName,
                                FileNavigation = Navigation.Clean(fileName),
                                Size = fileSize,
                                ContentType = Utility.GetMimeType(fileName)
                            }, (SessionState.Profile?.UserId).EnsureNotNullOrEmpty());
                        }
                    }
                }
                return Json(new { success = true, message = $"{postedFiles.Count:n0} file{(postedFiles.Count == 0 || postedFiles.Count > 1 ? "s" : string.Empty)}." });
            }
            catch (Exception ex)
            {
                ExceptionRepository.InsertException(ex, "文件上传失败.");
                return StatusCode(500, new { success = false, message = $"Error: {ex.Message}" });
            }
        }

        /// <summary>
        /// Uploads a file by manually selecting it for upload.
        /// </summary>
        /// <param name="postData"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost("ManualUpload/{givenPageNavigation}")]
        public IActionResult ManualUpload(string givenPageNavigation, IFormFile fileData)
        {
            SessionState.RequireCreatePermission();

            var pageNavigation = new NamespaceNavigation(givenPageNavigation);

            var page = PageRepository.GetPageInfoByNavigation(pageNavigation.Canonical).EnsureNotNull();

            if (fileData != null)
            {
                var fileSize = fileData.Length;
                if (fileSize > 0)
                {
                    if (fileSize > GlobalConfiguration.MaxAttachmentFileSize)
                    {
                        return Content("附件无法保存, 文件过大");
                    }

                    var fileName = HttpUtility.UrlDecode(fileData.FileName);

                    PageFileRepository.UpsertPageFile(new PageFileAttachment()
                    {
                        Data = Utility.ConvertHttpFileToBytes(fileData),
                        CreatedDate = DateTime.UtcNow,
                        PageId = page.Id,
                        Name = fileName,
                        FileNavigation = Navigation.Clean(fileName),
                        Size = fileSize,
                        ContentType = Utility.GetMimeType(fileName)
                    }, (SessionState.Profile?.UserId).EnsureNotNullOrEmpty());

                    return Content("成功");
                }
            }

            return Content("失败");
        }

        /// <summary>
        /// Allows a user to delete a page attachment from a page.
        /// </summary>
        /// <param name="navigation"></param>
        /// <returns></returns>
        [HttpPost("Detach/{givenPageNavigation}/{givenFileNavigation}/{pageRevision}")]
        public ActionResult Detach(string givenPageNavigation, string givenFileNavigation, int pageRevision)
        {
            SessionState.RequireDeletePermission();

            PageFileRepository.DetachPageRevisionAttachment(
                new NamespaceNavigation(givenPageNavigation).Canonical,
                new NamespaceNavigation(givenFileNavigation).Canonical, pageRevision);

            return Content("Success");
        }


        /// <summary>
        /// Gets a file from the database, converts it to a PNG with optional scaling and returns it to the client.
        /// </summary>
        /// <param name="navigation"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet("Emoji/{givenPageNavigation}")]
        public ActionResult Emoji(string givenPageNavigation)
        {
            SessionState.RequireViewPermission();

            var pageNavigation = Navigation.Clean(givenPageNavigation);

            if (string.IsNullOrEmpty(pageNavigation) == false)
            {
                string scale = GetQueryValue("Scale", "100");

                string shortcut = $"%%{pageNavigation.ToLower()}%%";
                var emoji = GlobalConfiguration.Emojis.Where(o => o.Shortcut == shortcut).FirstOrDefault();
                if (emoji != null)
                {
                    //Do we have this scale cached already?
                    var scaledImageCacheKey = WikiCacheKey.Build(WikiCache.Category.Emoji, [shortcut, scale]);
                    if (WikiCache.TryGet<ImageCacheItem>(scaledImageCacheKey, out var cachedEmoji))
                    {
                        return File(cachedEmoji.Bytes, cachedEmoji.ContentType);
                    }

                    var imageCacheKey = WikiCacheKey.Build(WikiCache.Category.Emoji, [shortcut]);
                    emoji.ImageData = WikiCache.Get<byte[]>(imageCacheKey);
                    if (emoji.ImageData == null)
                    {
                        //We don't get the bytes by default, that would be a lot of RAM for all the thousands of images.
                        emoji.ImageData = EmojiRepository.GetEmojiByName(emoji.Name)?.ImageData;

                        if (emoji.ImageData == null)
                        {
                            return NotFound($"Emoji {pageNavigation} 无法找到");
                        }

                        WikiCache.Put(imageCacheKey, emoji.ImageData);
                    }

                    if (emoji.ImageData != null)
                    {
                        var decompressedImageBytes = Utility.Decompress(emoji.ImageData);

                        var img = SixLabors.ImageSharp.Image.Load(new MemoryStream(decompressedImageBytes));

                        int customScalePercent = int.Parse(scale);
                        if (customScalePercent > 500)
                        {
                            customScalePercent = 500;
                        }

                        var (Width, Height) = Utility.ScaleToMaxOf(img.Width, img.Height, GlobalConfiguration.DefaultEmojiHeight);

                        //Adjust to any specified scaling.
                        Height = (int)(Height * (customScalePercent / 100.0));
                        Width = (int)(Width * (customScalePercent / 100.0));

                        //Adjusting by a ratio (and especially after applying additional scaling) may have caused one
                        //  dimension to become very small (or even negative). So here we will check the height and width
                        //  to ensure they are both at least n pixels and adjust both dimensions.
                        if (Height < 16)
                        {
                            Height += 16 - Height;
                            Width += 16 - Height;
                        }
                        if (Width < 16)
                        {
                            Height += 16 - Width;
                            Width += 16 - Width;
                        }

                        if (emoji.MimeType?.ToLower() == "image/gif")
                        {
                            var resized = ResizeGifImage(decompressedImageBytes, Width, Height);
                            var itemCache = new ImageCacheItem(resized, "image/gif");
                            WikiCache.Put(scaledImageCacheKey, itemCache);
                            return File(itemCache.Bytes, itemCache.ContentType);
                        }
                        else
                        {
                            using var image = Images.ResizeImage(img, Width, Height);
                            using var ms = new MemoryStream();
                            image.SaveAsPng(ms);

                            var itemCache = new ImageCacheItem(ms.ToArray(), "image/png");
                            WikiCache.Put(scaledImageCacheKey, itemCache);

                            return File(itemCache.Bytes, itemCache.ContentType);
                        }
                    }
                }
            }

            return NotFound($"Emoji {pageNavigation} 无法找到");
        }
    }
}
