using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TweetDuck.Core.Other;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;

namespace TweetDuck.Core.Management{
    sealed class ProfileManager{
        private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
        private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");

        [Flags]
        public enum Items{
            None = 0,
            UserConfig = 1,
            SystemConfig = 2,
            Session = 4,
            PluginData = 8,
            All = UserConfig|SystemConfig|Session|PluginData
        }

        public bool IsRestarting { get; private set; }

        private readonly string file;
        private readonly PluginManager plugins;

        public ProfileManager(string file, PluginManager plugins){
            this.file = file;
            this.plugins = plugins;
        }

        public bool Export(Items items){
            try{
                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
                    if (items.HasFlag(Items.UserConfig)){
                        stream.WriteFile("config", Program.UserConfigFilePath);
                    }

                    if (items.HasFlag(Items.SystemConfig)){
                        stream.WriteFile("system", Program.SystemConfigFilePath);
                    }

                    if (items.HasFlag(Items.PluginData)){
                        stream.WriteFile("plugin.config", Program.PluginConfigFilePath);

                        foreach(Plugin plugin in plugins.Plugins){
                            foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
                                try{
                                    stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
                                }catch(ArgumentOutOfRangeException e){
                                    FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
                                }
                            }
                        }
                    }

                    if (items.HasFlag(Items.Session)){
                        stream.WriteFile("cookies", CookiesPath);
                    }

                    stream.Flush();
                }

                return true;
            }catch(Exception e){
                Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
                return false;
            }
        }

        public Items FindImportItems(){
            Items items = Items.None;

            try{
                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
                    string key;

                    while((key = stream.SkipFile()) != null){
                        switch(key){
                            case "config":
                                items |= Items.UserConfig;
                                break;

                            case "system":
                                items |= Items.SystemConfig;
                                break;

                            case "plugin.config":
                            case "plugin.data":
                                items |= Items.PluginData;
                                break;

                            case "cookies":
                                items |= Items.Session;
                                break;
                        }
                    }
                }
            }catch(Exception){
                items = Items.None;
            }

            return items;
        }

        public bool Import(Items items){
            try{
                HashSet<string> missingPlugins = new HashSet<string>();

                using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
                    CombinedFileStream.Entry entry;

                    while((entry = stream.ReadFile()) != null){
                        switch(entry.KeyName){
                            case "config":
                                if (items.HasFlag(Items.UserConfig)){
                                    entry.WriteToFile(Program.UserConfigFilePath);
                                }

                                break;

                            case "system":
                                if (items.HasFlag(Items.SystemConfig)){
                                    entry.WriteToFile(Program.SystemConfigFilePath);
                                    IsRestarting = true;
                                }

                                break;

                            case "plugin.config":
                                if (items.HasFlag(Items.PluginData)){
                                    entry.WriteToFile(Program.PluginConfigFilePath);
                                }

                                break;

                            case "plugin.data":
                                if (items.HasFlag(Items.PluginData)){
                                    string[] value = entry.KeyValue;

                                    entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);

                                    if (!plugins.IsPluginInstalled(value[0])){
                                        missingPlugins.Add(value[0]);
                                    }
                                }

                                break;

                            case "cookies":
                                if (items.HasFlag(Items.Session)){
                                    entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
                                    IsRestarting = true;
                                }

                                break;
                        }
                    }
                }

                if (missingPlugins.Count > 0){
                    FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
                }

                return true;
            }catch(Exception e){
                Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, e);
                return false;
            }
        }

        public static void ImportCookies(){
            if (File.Exists(TempCookiesPath)){
                try{
                    if (File.Exists(CookiesPath)){
                        File.Delete(CookiesPath);
                    }

                    File.Move(TempCookiesPath, CookiesPath);
                }catch(Exception e){
                    Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
                }
            }
        }

        public static void DeleteCookies(){
            try{
                if (File.Exists(CookiesPath)){
                    File.Delete(CookiesPath);
                }
            }catch(Exception e){
                Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
            }
        }

        private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
            if (Directory.Exists(root)){
                int rootLength = root.Length;
                return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
            }
            else{
                return Enumerable.Empty<PathInfo>();
            }
        }

        private sealed class PathInfo{
            public string Full { get; }
            public string Relative { get; }

            public PathInfo(string fullPath, int rootLength){
                this.Full = fullPath;
                this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
            }
        }
    }

    static class ProfileManagerExtensions{
        public static bool NeedsRestart(this ProfileManager.Items items){
            return items.HasFlag(ProfileManager.Items.SystemConfig) || items.HasFlag(ProfileManager.Items.Session);
        }
    }
}