using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Pixiview.Illust;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Pixiview.Utils
{
    public static class Stores
    {
        public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
        public static readonly string CacheFolder = FileSystem.CacheDirectory;

        private const string favoriteFile = "favorites.json";
        private const string globalFile = "global.json";
        private const string imageFolder = "img-original";
        private const string previewFolder = "img-master";
        private const string ugoiraFolder = "img-zip-ugoira";
        private const string illustFile = "illust.json";

        private const string pagesFolder = "pages";
        private const string preloadsFolder = "preloads";
        private const string thumbFolder = "img-thumb";
        private const string userFolder = "user-profile";
        //private const string recommendsFolder = "recommends";

        public static bool NetworkAvailable
        {
            get
            {
                try
                {
                    return Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.Internet;
                }
                catch
                {
                    return false;
                }
            }
        }

        public static FavoriteList Favorites => GetFavoriteObject().Illusts;
        public static string FavoritesPath => Path.Combine(PersonalFolder, favoriteFile);
        public static DateTime FavoritesLastUpdated { get; set; } = DateTime.Now;

        private static IllustFavorite favoriteObject;

        public static IllustFavorite GetFavoriteObject(bool force = false)
        {
            if (force || favoriteObject == null)
            {
                var favorites = LoadFavoritesIllusts();
                if (favorites != null)
                {
                    favoriteObject = favorites;
                }
                else
                {
                    favoriteObject = new IllustFavorite
                    {
                        Illusts = new FavoriteList()
                    };
                }
            }
            return favoriteObject;
        }

        public static IllustFavorite LoadFavoritesIllusts(string file = null)
        {
            if (file == null)
            {
                file = FavoritesPath;
            }
            return ReadObject<IllustFavorite>(file);
        }

        public static void SaveFavoritesIllusts()
        {
            var file = FavoritesPath;
            var data = GetFavoriteObject();
            data.LastFavoriteUtc = DateTime.UtcNow;
            WriteObject(file, data);
        }

        public static string LoadUgoiraImage(string zip, string frame)
        {
            var file = Path.Combine(PersonalFolder, ugoiraFolder, zip, frame);
            if (File.Exists(file))
            {
                return file;
            }
            return null;
        }

        public static string SaveUgoiraImage(string zip, string frame, byte[] data)
        {
            try
            {
                var directory = Path.Combine(PersonalFolder, ugoiraFolder, zip);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                var file = Path.Combine(directory, frame);
                File.WriteAllBytes(file, data);
                return file;
            }
            catch (Exception ex)
            {
                App.DebugError("save.ugoira", $"failed to save ugoira frame: {zip}/{frame}, error: {ex.Message}");
                return null;
            }
        }

        public static string GetUgoiraPath(string url, string ext)
        {
            return Path.Combine(PersonalFolder, ugoiraFolder, Path.GetFileNameWithoutExtension(url) + ext);
        }

        private static T ReadObject<T>(string file)
        {
            string content = null;
            if (File.Exists(file))
            {
                try
                {
                    content = File.ReadAllText(file);
                }
                catch (Exception ex)
                {
                    App.DebugError("read", $"failed to read file: {file}, error: {ex.Message}");
                }
            }
            else
            {
                //App.DebugError("read", $"file not found: {file}");
                return default;
            }
            try
            {
                return JsonConvert.DeserializeObject<T>(content);
            }
            catch (Exception ex)
            {
                App.DebugError("read", $"failed to parse illust JSON object, error: {ex.Message}");
                return default;
            }
        }

        private static void WriteObject(string file, object obj)
        {
            var dir = Path.GetDirectoryName(file);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            string content;
            try
            {
                content = JsonConvert.SerializeObject(obj, Formatting.None);
            }
            catch (Exception ex)
            {
                App.DebugError("write", $"failed to serialize object, error: {ex.Message}");
                return;
            }

            try
            {
                File.WriteAllText(file, content, Encoding.UTF8);
            }
            catch (Exception ex)
            {
                App.DebugError("write", $"failed to write file: {file}, error: {ex.Message}");
            }
        }

        public static IllustData LoadIllustData(bool force = false)
        {
            var file = Path.Combine(PersonalFolder, illustFile);
            var result = HttpUtility.LoadObject<IllustData>(
                file,
                Configs.UrlIllustList,
                Configs.Referer,
                out _,
                force: force);
            if (result == null || result.error)
            {
                App.DebugPrint($"error when load illust data: {result?.message}, force({force})");
            }
            return result;
        }

        public static IllustRankingData LoadIllustRankingData(string mode, string date, int page, out string error, bool force = false)
        {
            var file = Path.Combine(CacheFolder, mode, $"{date}_{page}.json");
            string query = $"mode={mode}";
            if (mode != "male" && mode != "male_r18")
            {
                query += "&content=illust";
            }
            if (date != null)
            {
                query += $"&date={date}";
            }
            var referer = string.Format(Configs.RefererIllustRanking, query);
            if (page > 1)
            {
                query += $"&p={page}";
            }
            query += "&format=json";
            var result = HttpUtility.LoadObject<IllustRankingData>(
                file,
                string.Format(Configs.UrlIllustRanking, query),
                referer,
                out error,
                namehandler: rst =>
                {
                    return Path.Combine(CacheFolder, mode, $"{rst.date}_{page}.json");
                },
                header: headers =>
                {
                    headers.Add("X-Requested-With", "XMLHttpRequest");
                },
                force: force);
            if (result == null)
            {
                App.DebugPrint($"error when load ranking data: mode({mode}), date({date}), page({page}), force({force})");
            }
            return result;
        }

        public static IllustRecommendsData LoadIllustRecommendsInitData(string id)
        {
            //var file = Path.Combine(CacheFolder, recommendsFolder, $"{id}.json");
            var result = HttpUtility.LoadObject<IllustRecommendsData>(
                null,
                string.Format(Configs.UrlIllustRecommendsInit, id),
                string.Format(Configs.RefererIllust, id),
                out _);
            if (result == null || result.error)
            {
                App.DebugPrint($"error when load recommends init data: {result?.message}");
            }
            return result;
        }

        public static IllustRecommendsData LoadIllustRecommendsListData(string id, string[] ids)
        {
            if (ids == null || ids.Length == 0)
            {
                return null;
            }
            var ps = string.Concat(ids.Select(i => $"illust_ids%5B%5D={i}&"));
            var result = HttpUtility.LoadObject<IllustRecommendsData>(
                null,
                string.Format(Configs.UrlIllustRecommendsList, ps),
                string.Format(Configs.RefererIllust, id),
                out _);
            if (result == null || result.error)
            {
                App.DebugPrint($"error when load recommends list data: {result?.message}");
            }
            return result;
        }

        public static IllustGlobalData LoadGlobalData(bool force = false)
        {
            var file = Path.Combine(PersonalFolder, globalFile);
            var result = HttpUtility.LoadObject<IllustGlobalData>(
                file,
                Configs.Prefix,
                null,
                out _,
                force: force,
                header: h => { },
                action: content =>
                {
                    var index = content.IndexOf(Configs.SuffixGlobal);
                    if (index > 0)
                    {
                        index += Configs.SuffixGlobalLength;
                        var end = content.IndexOf('\'', index);
                        if (end > index)
                        {
                            content = content.Substring(index, end - index);
                        }
                    }
                    return content;
                });
            if (result == null)
            {
                App.DebugPrint($"error when load global data, is null");
            }
            else
            {
#if LOG
                App.DebugPrint($"current csrf token: {result.token}");
#endif
                Configs.CsrfToken = result.token;
            }
            return result;
        }

        public static string AddBookmark(string id)
        {
            if (string.IsNullOrEmpty(Configs.CsrfToken))
            {
                return null;
            }
            var content = new StringContent(
                "{\"illust_id\":\"" + id + "\",\"restrict\":0,\"comment\":\"\",\"tags\":[]}",
                Encoding.UTF8,
                Configs.AcceptJson);
            var result = HttpUtility.LoadObject<IllustResponse<BookmarkResultData>>(
                null,
                Configs.BookmarkAdd,
                Configs.Referer,
                out string error,
                force: true,
                post: content);
            if (error != null)
            {
                App.DebugPrint($"failed to add bookmark, error: {error}");
            }
            else if (result == null || result.error || result.body == null)
            {
                App.DebugPrint($"failed to add bookmark, message: {result?.message}");
            }
            else
            {
#if LOG
                App.DebugPrint($"successs, bookmark id: {result.body.last_bookmark_id}, status: {result.body.stacc_status_id}");
#endif
                return result.body.last_bookmark_id;
            }
            return null;
        }

        public static bool DeleteBookmark(string id)
        {
            if (string.IsNullOrEmpty(Configs.CsrfToken))
            {
                return false;
            }
            var content = new StringContent(
                "mode=delete_illust_bookmark&bookmark_id=" + id,
                Encoding.UTF8,
                Configs.AcceptUrlEncoded);
            var result = HttpUtility.LoadObject<object>(
                null,
                Configs.BookmarkRpc,
                Configs.Referer,
                out string error,
                force: true,
                nojson: true,
                post: content);
            if (error != null)
            {
                App.DebugPrint($"failed to delete bookmark, error: {error}");
                return false;
            }
            else if (result == null)
            {
                App.DebugPrint("failed to delete bookmark, result is null");
                return false;
            }
#if LOG
            App.DebugPrint($"successs, delete bookmark");
#endif
            return true;
        }

        public static IllustPreloadBody LoadIllustPreloadData(string id, bool force = false)
        {
            var file = Path.Combine(CacheFolder, preloadsFolder, $"{id}.json");
            var result = HttpUtility.LoadObject<IllustPreloadBody>(
                file,
                string.Format(Configs.UrlIllust, id),
                null,
                out _,
                force: force,
                action: content =>
                {
                    var index = content.IndexOf(Configs.SuffixPreload);
                    if (index > 0)
                    {
                        index += Configs.SuffixPreloadLength;
                        var end = content.IndexOf('\'', index);
                        if (end > index)
                        {
                            content = content.Substring(index, end - index);
                        }
                    }
                    return content;
                });
            if (result == null)
            {
                App.DebugPrint($"error when load preload data: force({force})");
            }
            return result;
        }

        public static IllustPageData LoadIllustPageData(string id, out string error, bool force = false)
        {
            var file = Path.Combine(CacheFolder, pagesFolder, $"{id}.json");
            var result = HttpUtility.LoadObject<IllustPageData>(
                file,
                string.Format(Configs.UrlIllustPage, id),
                string.Format(Configs.RefererIllust, id),
                out _,
                force: force);
            if (result == null || result.error)
            {
                error = result?.message ?? "result is null";
                App.DebugPrint($"error when load page data: {error}, force({force})");
                return null;
            }
            error = null;
            return result;
        }

        public static IllustUgoiraData LoadIllustUgoiraData(string id, bool force = false)
        {
            var file = Path.Combine(PersonalFolder, ugoiraFolder, $"{id}.json");
            var result = HttpUtility.LoadObject<IllustUgoiraData>(
                file,
                string.Format(Configs.UrlIllustUgoira, id),
                string.Format(Configs.RefererIllust, id),
                out _,
                force: force);
            if (result == null || result.error)
            {
                App.DebugPrint($"error when load ugoira data: {result?.message}, force({force})");
            }
            return result;
        }

        public static IllustUserListData LoadIllustUserInitData(string userId)
        {
            var list = HttpUtility.LoadObject<IllustUserListData>(
                null,
                string.Format(Configs.UrlIllustUserAll, userId),
                string.Format(Configs.RefererIllustUser, userId),
                out _);
            if (list == null || list.error)
            {
                App.DebugPrint($"error when load user data: {list?.message}");
            }
            return list;
        }

        public static IllustUserData LoadIllustUserData(string userId, string[] ids, bool firstPage)
        {
            if (ids == null || ids.Length == 0)
            {
                return null;
            }
            var ps = string.Concat(ids.Select(i => $"ids%5B%5D={i}&"));
            var result = HttpUtility.LoadObject<IllustUserData>(
                null,
                string.Format(Configs.UrlIllustUserArtworks, userId, ps, firstPage ? 1 : 0),
                string.Format(Configs.RefererIllustUser, userId),
                out _);
            if (result == null || result.error)
            {
                App.DebugPrint($"error when load user illust data: {result?.message}");
            }
            return result;
        }

        private static readonly Regex regexIllust = new Regex(
            @"book_id\[\]"" value=""([0-9]+)"".*data-src=""([^""]+)"".*data-id=""([0-9]+)"".*" +
            @"data-tags=""([^""]+)"".*data-user-id=""([0-9]+)"".*" +
            @"class=""title"" title=""([^""]+)"".*data-user_name=""([^""]+)"".*" +
            @"_bookmark-icon-inline""></i>([0-9]+)</a>",
            RegexOptions.Compiled);

        public static IllustItem[] LoadOnlineFavorites()
        {
            var list = new List<IllustItem>();
            int p = 1;
            while (p > 0)
            {
                var url = Configs.UrlFavoriteList;
                if (p > 1)
                {
                    url += $"&p={p}";
                }
                var array = HttpUtility.LoadObject(
                    null,
                    url,
                    null,
                    out _,
                    action: content =>
                    {
                        // page
                        p = -1;
                        var index = content.IndexOf("<span class=\"next\"><a href=\"?rest=show&amp;p=");
                        if (index > 0)
                        {
                            var page = content.Substring(index + 45, content.IndexOf('\"', index + 45) - index - 45);
                            if (int.TryParse(page, out var next))
                            {
                                p = next;
                            }
                        }
                        // list
                        index = content.IndexOf("<li class=\"image-item\">");
                        if (index < 0)
                        {
                            return null;
                        }
                        content = content.Substring(index + 23);
                        index = content.IndexOf("</li></ul><div class=\"clear\"></div>");
                        if (index < 0)
                        {
                            return null;
                        }
                        return content.Substring(0, index);
                    },
                    @return: content =>
                    {
                        if (content == null)
                        {
                            return null;
                        }
                        var lines = content.Split("</li><li class=\"image-item\">");
                        var illusts = new IllustItem[lines.Length];
                        for (var i = 0; i < illusts.Length; i++)
                        {
                            var line = lines[i];
                            var m = regexIllust.Match(line);
                            if (m.Success)
                            {
                                illusts[i] = new IllustItem
                                {
                                    IsFavorite = true,
                                    BookmarkId = m.Groups[1].Value,
                                    ImageUrl = m.Groups[2].Value,
                                    Id = m.Groups[3].Value,
                                    Tags = m.Groups[4].Value.Split(' '),
                                    UserId = m.Groups[5].Value,
                                    Title = m.Groups[6].Value,
                                    UserName = m.Groups[7].Value,
                                    RatingCount = int.Parse(m.Groups[8].Value)
                                };
                            }
                        }
                        return illusts;
                    });
                if (array != null && array.Length > 0)
                {
                    list.AddRange(array);
                }
            }
            return list.ToArray();
        }

        public static ImageSource LoadIllustImage(string url)
        {
            return LoadImage(url, PersonalFolder, imageFolder);
        }

        public static ImageSource LoadPreviewImage(string url, bool downloading = true, bool force = false)
        {
            return LoadImage(url, PersonalFolder, previewFolder, downloading, force);
        }

        public static ImageSource LoadThumbnailImage(string url)
        {
            return LoadImage(url, CacheFolder, thumbFolder);
        }

        public static ImageSource LoadUserProfileImage(string url)
        {
            return LoadImage(url, CacheFolder, userFolder);
        }

        public static bool CheckIllustImage(string url)
        {
            var file = Path.Combine(PersonalFolder, imageFolder, Path.GetFileName(url));
            return File.Exists(file);
        }
        public static bool CheckUgoiraVideo(string url)
        {
            var file = Path.Combine(PersonalFolder, ugoiraFolder, Path.GetFileNameWithoutExtension(url) + ".mp4");
            return File.Exists(file);
        }

        public static string GetPreviewImagePath(string url)
        {
            var file = Path.Combine(PersonalFolder, previewFolder, Path.GetFileName(url));
            if (File.Exists(file))
            {
                return file;
            }
            return null;
        }

        private static ImageSource LoadImage(string url, string working, string folder, bool downloading = true, bool force = false)
        {
            var file = Path.Combine(working, folder, Path.GetFileName(url));
            ImageSource image;
            if (!force && File.Exists(file))
            {
                try
                {
                    image = ImageSource.FromFile(file);
                }
                catch (Exception ex)
                {
                    App.DebugError("image.load", $"failed to load image from file: {file}, error: {ex.Message}");
                    image = null;
                }
            }
            else
            {
                image = null;
            }
            if (downloading && image == null)
            {
                file = HttpUtility.DownloadImage(url, working, folder);
                if (file != null)
                {
                    return ImageSource.FromFile(file);
                }
            }
            return image;
        }
    }

    public class IllustFavorite
    {
        public DateTime LastFavoriteUtc { get; set; }
        public FavoriteList Illusts { get; set; }
    }

    public class FavoriteList : List<IllustItem>
    {
        public bool Changed { get; private set; }

        public FavoriteList() : base() { }
        public FavoriteList(IEnumerable<IllustItem> collection) : base(collection) { }

        public new void Insert(int index, IllustItem item)
        {
            base.Insert(index, item);
            Changed = true;
        }

        public new void InsertRange(int index, IEnumerable<IllustItem> collection)
        {
            base.InsertRange(index, collection);
            Changed = true;
        }

        public new void RemoveAt(int index)
        {
            base.RemoveAt(index);
            Changed = true;
        }

        public FavoriteList Reload()
        {
            Changed = false;
            return this;
        }
    }

    public enum SyncType
    {
        None = 0,
        Prompt,
        AutoSync
    }

    public static class Configs
    {
        public const string ProfileNameKey = "name";
        public const string ProfileIdKey = "pixiv_id";
        public const string ProfileImageKey = "profile_img";
        public const string CookieKey = "cookies";
        public const string UserIdKey = "user_id";
        public const string IsOnR18Key = "is_on_r18";
        public const string SyncFavTypeKey = "sync_fav_type";
        public const string IsProxiedKey = "is_proxied";
        public const string HostKey = "host";
        public const string PortKey = "port";
        public const string QueryModeKey = "query_mode";
        public const string QueryTypeKey = "query_type";
        public const string QueryDateKey = "query_date";

        public const int MaxPageThreads = 3;
        public const int MaxThreads = 4;
        public const string Referer = "https://www.pixiv.net/";
        public const string RefererIllust = "https://www.pixiv.net/artworks/{0}";
        public const string RefererIllustRanking = "https://www.pixiv.net/ranking.php?{0}";
        public const string RefererIllustUser = "https://www.pixiv.net/users/{0}/illustrations";

        public static bool IsOnR18;
        public static SyncType SyncFavType;
        public static WebProxy Proxy;
        public static string Prefix => Proxy == null ?
            "https://hk.tsanie.us/reverse/" :
            "https://www.pixiv.net/";
        public static string UserId { get; private set; }
        public static string Cookie { get; private set; }
        public static string CsrfToken;

        public static void SetUserId(string userId, bool save = false)
        {
            UserId = userId;
            if (!save)
            {
                return;
            }
            if (userId == null)
            {
                Preferences.Remove(UserIdKey);
            }
            else
            {
                Preferences.Set(UserIdKey, userId);
            }
        }

        public static void SetCookie(string cookie, bool save = false)
        {
            Cookie = cookie;
            if (!save)
            {
                return;
            }
            if (cookie == null)
            {
                Preferences.Remove(CookieKey);
            }
            else
            {
                Preferences.Set(CookieKey, cookie);
            }
        }

#if __IOS__
        public static System.Threading.Tasks.Task<bool> RequestCookieContainer(WebKit.WKHttpCookieStore cookieStore)
        {
            var task = new System.Threading.Tasks.TaskCompletionSource<bool>();
            cookieStore.GetAllCookies(cookies =>
            {
                var list = new List<string>();
                foreach (var c in cookies)
                {
#if DEBUG
                    App.DebugPrint($"domain: {c.Domain}, path: {c.Path}, {c.Name}={c.Value}, http only: {c.IsHttpOnly}, session only: {c.IsSessionOnly}");
#endif
                    var domain = c.Domain;
                    if (domain == null)
                    {
                        continue;
                    }
                    if (domain != "www.pixiv.net" && domain != ".pixiv.net")
                    {
                        continue;
                    }
                    list.Add($"{c.Name}={c.Value}");
                }
                var cookie = string.Join("; ", list);
                Cookie = cookie;

                Preferences.Set(CookieKey, cookie);
                task.SetResult(true);
            });
            return task.Task;
        }
#endif

        public const string SuffixGlobal = " id=\"meta-global-data\" content='";
        public const int SuffixGlobalLength = 32;
        public const string SuffixPreload = " id=\"meta-preload-data\" content='";
        public const int SuffixPreloadLength = 33;  // SuffixPreload.Length

        public static string UrlIllustList => Prefix + "ajax/top/illust?mode=all&lang=zh";
        public static string UrlIllust => Prefix + "artworks/{0}";
        public static string UrlIllustRanking => Prefix + "ranking.php?{0}";
        public static string UrlIllustUserAll => Prefix + "ajax/user/{0}/profile/all?lang=zh";
        public static string UrlIllustUserArtworks => Prefix + "ajax/user/{0}/profile/illusts?{1}work_category=illust&is_first_page={2}&lang=zh";
        public static string UrlIllustPage => Prefix + "ajax/illust/{0}/pages?lang=zh";
        public static string UrlIllustUgoira => Prefix + "ajax/illust/{0}/ugoira_meta?lang=zh";
        public static string UrlIllustRecommendsInit => Prefix + "ajax/illust/{0}/recommend/init?limit=18&lang=zh";
        public static string UrlIllustRecommendsList => Prefix + "ajax/illust/recommend/illusts?{0}lang=zh";
        public static string UrlFavoriteList => Prefix + "bookmark.php?rest=show";

        public static string BookmarkAdd => Prefix + "ajax/illusts/bookmarks/add";
        public static string BookmarkRpc => Prefix + "rpc/index.php";

        public const string UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
        public const string AcceptImage = "image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5";
        public const string AcceptJson = "application/json";
        public const string AcceptUrlEncoded = "application/x-www-form-urlencoded";
        //public const string AcceptEncoding = "gzip, deflate";
        public const string AcceptLanguage = "zh-cn";

        private const string URL_PREVIEW = "https://i.pximg.net/c/360x360_70";

        public static string GetThumbnailUrl(string url)
        {
            if (url == null)
            {
                return null;
            }
            url = url.ToLower().Replace("/custom-thumb/", "/img-master/");
            var index = url.LastIndexOf("_square1200.jpg");
            if (index < 0)
            {
                index = url.LastIndexOf("_custom1200.jpg");
            }
            if (index > 0)
            {
                url = url.Substring(0, index) + "_master1200.jpg";

                var start = url.IndexOf("/img-master/");
                if (start > 0)
                {
                    url = URL_PREVIEW + url.Substring(start);
                }
            }
            return url;
        }
    }

    public static class Routes
    {
        public const string Illust = "illust";
        public const string Detail = "detail";
        public const string Follow = "follow";
        public const string Recommends = "recommends";
        public const string ByUser = "byuser";
        public const string Ranking = "ranking";
        public const string Favorites = "favorites";
        public const string Option = "option";
    }
}
