using GCMM.Properties;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GCMM
{
    partial class MainForm
    {

        public HashSet<string> GetInstalledMods()
        {
            bool disabled = false;
            if (!Directory.Exists(GamePath("\\Plugins")))
                if (Directory.Exists(GamePath("\\Plugins_Disabled")))
                    disabled = true;
                else return new HashSet<string>();
            var installed = new HashSet<string>();
            foreach (var modPath in Directory.GetFiles(GamePath(disabled ? @"\Plugins_Disabled" : @"\Plugins"), "*.dll"))
            {
                try
                {
                    var an = AssemblyName.GetAssemblyName(modPath);
                    if (an.Name == "0Harmony") continue;
                    //Use filename to avoid differences between repository & assembly name casing
                    var mod = new ModInfo { Name = Path.GetFileNameWithoutExtension(modPath), Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) };
                    AddUpdateModInList(mod);
                    installed.Add(mod.Name);
                }
                catch (BadImageFormatException)
                { //Not a .NET assembly
                }
            }
            return installed;
        }

        public async void GetAvailableMods()
        {
            bool preview = GetExe()?.Contains("Preview") ?? false;
            using (var client = GetClient())
            {
                string str = await client.DownloadStringTaskAsync("https://exmods.org/mods/modlist.tsv");
                foreach (string line in str.Trim().Split('\n'))
                {
                    var sp = line.Split('\t');
                    if (sp.Length < 2) continue;
                    var mod = new ModInfo
                    {
                        Author = sp[0].Trim(),
                        Name = sp[1].Trim()
                    };
                    if (await FetchModInfo(mod, preview)) //If it's actually a mod
                        AddUpdateModInList(mod);
                }
            }
        }

        public async Task<bool> FetchModInfo(ModInfo mod, bool preview)
        {
            string repoURL = "/api/v1/repos/" + mod.Author + "/" + mod.Name + "/releases";
            using (var client = GetClient())
            {
                var arr = JArray.Parse(await client.DownloadStringTaskAsync(repoURL));
                var release = arr.FirstOrDefault(rel =>
                {
                    if ((bool) rel["prerelease"] || (bool) rel["draft"])
                        return false;
                    var vs = rel["tag_name"].ToString();
                    int ind = vs.IndexOf('-');
                    if (ind != -1)
                    {
                        if (vs.Substring(ind + 1).Equals("preview", StringComparison.InvariantCultureIgnoreCase)
                            && !preview)
                            return false;
                    }
                    return true;
                });
                if (release == null)
                    return false;
                var verstr = release["tag_name"].ToString().Replace("v", "");
                int index = verstr.IndexOf('-');
                if (index != -1)
                    verstr = verstr.Substring(0, index);
                
                JToken asset;
                if (release["assets"].Count() == 1)
                    asset = release["assets"].First;
                else
                    asset = release["assets"].FirstOrDefault(token =>
                    {
                        string name = token["name"].ToString();
                        return name == mod.Name + ".dll" || name == mod.Name + ".zip";
                    });
                
                mod.DownloadURL = asset?["browser_download_url"]?.ToString();
                mod.LastUpdated = (DateTime)release["published_at"];

                var ver = verstr.Split('.').Select(str => int.Parse(str)).ToArray();
                int getver(byte i) => ver.Length > i ? ver[i] : 0; //By default it sets values not present to -1, but we need them to be 0
                mod.LatestVersion = new Version(getver(0), getver(1), getver(2), getver(3));
                mod.UpdateDetails = release["name"] + "\n\n" + release["body"].ToString();
                try
                {
                    var obj = JObject.Parse(await client.DownloadStringTaskAsync("/api/v1/repos/" + mod.Author + "/" + mod.Name + "/contents/README.md"));
                    mod.Description = Encoding.UTF8.GetString(Convert.FromBase64String(obj["content"].ToString()));
                }
                catch(WebException)
                { //It returns a HTTP 500 if it doesn't exist...
                }
                return true;
            }
        }

        public void AddUpdateModInList(ModInfo mod)
        {
            if (mods.ContainsKey(mod.Name) ^ modlist.Items.ContainsKey(mod.Name)) //The ListView's keys aren't case sensitive
                throw new InvalidOperationException("The mod isn't present in one of the two places: " + mod.Name);
            ListViewItem item;
            if (modlist.Items.ContainsKey(mod.Name))
            {
                var omod = mods[mod.Name];
                item = modlist.Items[mod.Name];
                var items = item.SubItems;
                omod.Author = mod.Author ?? omod.Author;
                omod.Version = mod.Version ?? omod.Version; //If the object comes from the dictionary then it's directly modified (uninstall)
                omod.LatestVersion = mod.LatestVersion ?? omod.LatestVersion;
                omod.LastUpdated = mod.LastUpdated == default ? omod.LastUpdated : mod.LastUpdated;
                omod.Description = mod.Description ?? omod.Description;
                omod.DownloadURL = mod.DownloadURL ?? omod.DownloadURL;
                omod.UpdateDetails = mod.UpdateDetails ?? omod.UpdateDetails;
                items[1].Text = omod.Author ?? "";
                items[2].Text = (omod.Version ?? omod.LatestVersion)?.ToString();
                items[3].Text = omod.LastUpdated.ToString();
                item.Group = omod.Installed ? modlist.Groups["installed"] : modlist.Groups["available"];
                mod = omod;
            }
            else
            {
                mods.Add(mod.Name, mod);
                item = new ListViewItem(new[] { mod.Name, mod.Author ?? "", (mod.Version ?? mod.LatestVersion)?.ToString() ?? "", mod.LastUpdated.ToString() }, modlist.Groups[mod.Installed ? "installed" : "available"]);
                item.Name = mod.Name;
                modlist.Items.Add(item);
            }
            if (mod.LatestVersion != null && mod.Version != null && mod.Version < mod.LatestVersion)
                item.ForeColor = Color.Blue;
            else
                item.ForeColor = modlist.ForeColor;
        }

        public void CheckUninstalledMods(HashSet<string> installed)
        {
            foreach (string name in mods.Keys.Except(installed))
            {
                var mod = mods[name];
                mod.Version = null;
                AddUpdateModInList(mod);
            }
        }
    }
}