using System;
using System.IO;
using TweetDuck.Plugins.Enums;

namespace TweetDuck.Plugins{
    sealed class Plugin{
        private static readonly Version AppVersion = new Version(Program.VersionTag);

        public string Identifier { get; }
        public PluginGroup Group { get; }
        public PluginEnvironment Environments { get; }

        public string Name { get; }
        public string Description { get; }
        public string Author { get; }
        public string Version { get; }
        public string Website { get; }
        public string ConfigFile { get; }
        public string ConfigDefault { get; }
        public Version RequiredVersion { get; }

        public bool CanRun { get; }

        public bool HasConfig{
            get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
        }

        public string ConfigPath{
            get => HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
        }

        public bool HasDefaultConfig{
            get => ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
        }

        public string DefaultConfigPath{
            get => HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
        }

        private readonly string pathRoot;
        private readonly string pathData;

        private Plugin(PluginGroup group, string identifier, string pathRoot, string pathData, Builder builder){
            this.pathRoot = pathRoot;
            this.pathData = pathData;

            this.Group = group;
            this.Identifier = identifier;
            this.Environments = builder.Environments;

            this.Name = builder.Name;
            this.Description = builder.Description;
            this.Author = builder.Author;
            this.Version = builder.Version;
            this.Website = builder.Website;
            this.ConfigFile = builder.ConfigFile;
            this.ConfigDefault = builder.ConfigDefault;
            this.RequiredVersion = builder.RequiredVersion;

            this.CanRun = AppVersion >= RequiredVersion;
        }

        public string GetScriptPath(PluginEnvironment environment){
            if (Environments.HasFlag(environment)){
                string file = environment.GetPluginScriptFile();
                return file != null ? Path.Combine(pathRoot, file) : string.Empty;
            }
            else{
                return string.Empty;
            }
        }

        public string GetPluginFolder(PluginFolder folder){
            switch(folder){
                case PluginFolder.Root: return pathRoot;
                case PluginFolder.Data: return pathData;
                default: return string.Empty;
            }
        }

        public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
            string rootFolder = GetPluginFolder(folder);
            string fullPath = Path.Combine(rootFolder, relativePath);

            try{
                string folderPathName = new DirectoryInfo(rootFolder).FullName;
                DirectoryInfo currentInfo = new DirectoryInfo(fullPath); // initially points to the file, which is convenient for the Attributes check below
                DirectoryInfo parentInfo = currentInfo.Parent;

                while(parentInfo != null){
                    if (currentInfo.Exists && currentInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)){
                        return string.Empty; // no reason why a plugin should have files/folders with symlinks, junctions, or any other crap
                    }

                    if (parentInfo.FullName == folderPathName){
                        return fullPath;
                    }

                    currentInfo = parentInfo;
                    parentInfo = currentInfo.Parent;
                }
            }
            catch{
                // ignore
            }

            return string.Empty;
        }

        public override string ToString(){
            return Identifier;
        }

        public override int GetHashCode(){
            return Identifier.GetHashCode();
        }

        public override bool Equals(object obj){
            return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
        }

        // Builder

        public sealed class Builder{
            private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0);

            public string Name             { get; set; }
            public string Description      { get; set; } = string.Empty;
            public string Author           { get; set; } = "(anonymous)";
            public string Version          { get; set; } = string.Empty;
            public string Website          { get; set; } = string.Empty;
            public string ConfigFile       { get; set; } = string.Empty;
            public string ConfigDefault    { get; set; } = string.Empty;
            public Version RequiredVersion { get; set; } = DefaultRequiredVersion;

            public PluginEnvironment Environments { get; private set; } = PluginEnvironment.None;

            private readonly PluginGroup group;
            private readonly string pathRoot;
            private readonly string pathData;
            private readonly string identifier;

            public Builder(PluginGroup group, string name, string pathRoot, string pathData){
                this.group = group;
                this.pathRoot = pathRoot;
                this.pathData = pathData;
                this.identifier = group.GetIdentifierPrefix()+name;
            }

            public void AddEnvironment(PluginEnvironment environment){
                this.Environments |= environment;
            }

            public Plugin BuildAndSetup(){
                Plugin plugin = new Plugin(group, identifier, pathRoot, pathData, this);

                if (plugin.Name.Length == 0){
                    throw new InvalidOperationException("Plugin is missing a name in the .meta file");
                }

                if (plugin.Environments == PluginEnvironment.None){
                    throw new InvalidOperationException("Plugin has no script files");
                }

                if (plugin.Group == PluginGroup.Official){
                    if (plugin.RequiredVersion != AppVersion){
                        throw new InvalidOperationException("Plugin is not supported in this version of TweetDuck, this may indicate a failed update or an unsupported plugin that was not removed automatically");
                    }
                    else if (!string.IsNullOrEmpty(plugin.Version)){
                        throw new InvalidOperationException("Official plugins cannot have a version identifier");
                    }
                }

                // setup

                string configPath = plugin.ConfigPath, defaultConfigPath = plugin.DefaultConfigPath;

                if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
                    string dataFolder = plugin.GetPluginFolder(PluginFolder.Data);

                    try{
                        Directory.CreateDirectory(dataFolder);
                        File.Copy(defaultConfigPath, configPath, false);
                    }catch(Exception e){
                        throw new IOException($"Could not generate a configuration file for '{plugin.Identifier}' plugin: {e.Message}", e);
                    }
                }

                // done

                return plugin;
            }
        }
    }
}