using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Razor.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Encodings.Web;

namespace Kit.Helpers.Mvc.TagHelpers
{
    public class FileTagHelperContext
    {
        public enum LoadEntityType
        {
            Script = 1,
            Stylesheet = 2,
        }

        public enum PriorityChangeMode
        {
            Same = 1,
            After = 2,
        }

        public class File
        {
            public File(string url, LoadEntityType type, uint priority)
            {
                Url = url;
                Type = type;
                Priority = priority;
                Loaded = false;
            }

            public string Url { get; }
            public LoadEntityType Type { get; }
            public uint Priority { get; }
            public bool Loaded { get; }
        }

        public FileTagHelperContext(HttpContext? httpContext, bool needDynamicLoad, string allFilesLoadedEventName)
        {
            HttpContext = httpContext;
            IsDynamicLoad = needDynamicLoad;

            EventNameAllFilesLoaded = allFilesLoadedEventName;

            Files = new List<File>();

            _priority = 0;
        }

        public HttpContext? HttpContext { get; }
        public bool IsDynamicLoad { get; }

        public string EventNameAllFilesLoaded { get; }

        public bool NeedPrependDomain { get; init; }
        public bool NeedAppendVersion { get; init; }

        public List<File> Files { get; }


        private uint _priority;

        public void SetPriorityAfter()
        {
            _priority++;
        }

        public void AddFile(string url, LoadEntityType entityType, PriorityChangeMode mode)
        {
            if (Files.Any(x => x.Url == url)) return;
            if (mode == PriorityChangeMode.After) SetPriorityAfter();

            Files.Add(new File(url, entityType, _priority));
        }
    }

    [HtmlTargetElement(TagName, Attributes = $"[{PathExactAttributeName}^='~/']", TagStructure = TagStructure.WithoutEndTag)]
    [HtmlTargetElement(TagName, Attributes = TypeAttributeName, TagStructure = TagStructure.WithoutEndTag)]
    [HtmlTargetElement(TagName, Attributes = ContextAttributeName, TagStructure = TagStructure.WithoutEndTag)]
    public class FileTagHelper : Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper
    {
        private const string TagName = "file";
        private const string TypeAttributeName = "type";
        private const string PriorityModeAttributeName = "priority-mode";

        private const string ContextAttributeName = "asp-context";
        private const string PathExactAttributeName = "asp-path-exact";
        private const string PathIncludeAttributeName = "asp-path-include";
        private const string PathExcludeAttributeName = "asp-path-exclude";

#pragma warning disable
        /// <summary> Creates a new <see cref="FileTagHelper"/>. </summary>
        public FileTagHelper(IWebHostEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) : base(urlHelperFactory, htmlEncoder)
        {
            HostingEnvironment = hostingEnvironment;
            Cache = cacheProvider.Cache;
            FileVersionProvider = fileVersionProvider;
        }
#pragma warning enable

        /// <inheritdoc />
        public override int Order => -1000;


        [HtmlAttributeName(ContextAttributeName)]
        public FileTagHelperContext Context { get; set; }

        [HtmlAttributeName(PathExactAttributeName)]
        public string Path { get; set; }

        [HtmlAttributeName(TypeAttributeName)]
        public FileTagHelperContext.LoadEntityType Type { get; set; }

        [HtmlAttributeName(PriorityModeAttributeName)]
        public FileTagHelperContext.PriorityChangeMode PriorityChangeMode { get; set; } = FileTagHelperContext.PriorityChangeMode.Same;


        /// <summary> A comma separated list of globbed file patterns to load. The glob patterns are assessed relative to the application's 'webroot' setting. </summary>
        [HtmlAttributeName(PathIncludeAttributeName)]
        public string PathInclude { get; set; }

        /// <summary> A comma separated list of globbed file patterns to exclude from loading. The glob patterns are assessed relative to the application's 'webroot' setting. Must be used in conjunction with <see cref="PathInclude"/>. </summary>
        [HtmlAttributeName(PathExcludeAttributeName)]
        public string PathExclude { get; set; }

        /// <summary> Gets the <see cref="IWebHostEnvironment"/> for the application. </summary>
        protected internal IWebHostEnvironment HostingEnvironment { get; }

        /// <summary> Gets the <see cref="IMemoryCache"/> used to store globbed urls. </summary>
        protected internal IMemoryCache Cache { get; }

        /// <summary> Gets the <see cref="GlobbingUrlBuilder"/> used to populate included and excluded urls. </summary>
        // Internal for ease of use when testing.
        protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; }

        internal IFileVersionProvider FileVersionProvider { get; private set; }

        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            if (output == null) throw new ArgumentNullException(nameof(output));

            var builder = output.PostElement;
            builder.Clear();

            output.TagName = null;
            output.Content.SetContent(string.Empty);

            IEnumerable<string> urls = SelectUrls();

            urls = urls.Where(url =>
            {
                if (string.IsNullOrWhiteSpace(url)) return false;

                string mimeType = url.GetMimeType();

                switch (Type)
                {
                    case FileTagHelperContext.LoadEntityType.Stylesheet:
                        return url.GetMimeType() == ".css".GetMimeType();
                    case FileTagHelperContext.LoadEntityType.Script:
                        return url.GetMimeType() == ".js".GetMimeType();
                    default:
                        return false;
                }
            });

            urls.ForEach((url, index) =>
            {
                string urlFull = GetVersionedUrl(url);
                FileTagHelperContext.PriorityChangeMode mode = FileTagHelperContext.PriorityChangeMode.Same;

                if (index == 0)
                {
                    mode = PriorityChangeMode;
                }

                Context.AddFile(urlFull, Type, mode);
            });
        }

        private GlobbingUrlBuilder EnsureGlobbingUrlBuilder()
        {
            if (GlobbingUrlBuilder == null)
            {
                GlobbingUrlBuilder = new GlobbingUrlBuilder(HostingEnvironment.WebRootFileProvider, Cache, ViewContext.HttpContext.Request.PathBase);
            }

            return GlobbingUrlBuilder;
        }

        private IEnumerable<string> SelectUrls()
        {
            var result = new List<string>();
            if (Path != null)
            {
                result.Add(Path);
            }

            if (string.IsNullOrWhiteSpace(PathInclude) == false)
            {
                IReadOnlyList<string>? urls = EnsureGlobbingUrlBuilder().BuildUrlList(null, PathInclude, PathExclude);
                if (urls.IsNullOrEmpty() == false)
                {
                    result.AddRange(urls);
                }
            }

            return result;
        }


        private IFileVersionProvider EnsureFileVersionProvider()
        {
            if (FileVersionProvider == null)
            {
                FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService<IFileVersionProvider>();
            }

            return FileVersionProvider;
        }

        private string GetVersionedUrl(string url)
        {
            if (Context.NeedAppendVersion == true)
            {
                url = EnsureFileVersionProvider().AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, url);
            }

            if (Context.NeedPrependDomain && Context.HttpContext != null)
            {
                string rootUrl = Context.HttpContext.GetRootUrl();
                // Заменяем символ "~" на домен
                if (url.StartsWith("~"))
                {
                    url = rootUrl + url.Substring(1);
                }
                else if (url.StartsWith("/"))
                {
                    url = rootUrl + url;
                }
            }

            return url;
        }
    }
}