using Katteker;
using KattekerCreator.Helper;
using KattekerCreator.Types;
using McMaster.Extensions.CommandLineUtils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace KattekerCreator
{
    internal class Program : ProgramArguments
    {
        private static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        private static string MakeNsis => Path.Combine(BaseDirectory, "nsis", "makensis.exe");
        private AssemblyFileInfo _assemblyFileInfo;
        private TemporaryDirectory _tempDir;
        private Releases _releases;

        static int Main(string[] args)
        {
            return CommandLineApplication.Execute<Program>(args);
        }

        protected override int OnExecute(CommandLineApplication app)
        {
            //Override commandline arguments, if 'ConfigurationPath' is set.
            if (TryReadConfigFile(ConfigurationPath, out var dictionary))
            {
                OverridePropertyValues(this, dictionary);
            }

            //Create temp-dir
            using (_tempDir = TemporaryDirectory.Create())
            {
                _releases = new Releases(OutputDir);

                //Modify AppStub
                var appStubFile = ModifyAppStub();
                //Acquire infos from Executable.
                _assemblyFileInfo = new AssemblyFileInfo(ProgramFile, Version, _tempDir.Path);
                var configFile = CreateConfigFile();

                //Generate NSIS-Script
                var additionalFiles = new[]
                {
                    new PhysicalFile(appStubFile, Path.GetFileName(ProgramFile)),
                    new PhysicalFile(configFile, Path.Combine($"app-{_assemblyFileInfo.AssemblyVersion}", Path.GetFileName(configFile) ?? string.Empty))
                };
                var templateFile = GenerateNsisTemplate(additionalFiles);
                //Start makensis.exe
                var setupFilePath = CompileSetupScript(templateFile);
                //TODO Sign setup
                Utils.SignExecutable(CertificatePath, CertificatePassword, setupFilePath);
                //Copy to Output-Folder
                CopyToOutputFolder(setupFilePath);
                //Create/Modify RELEASE File
                var releaseEntry = AddPackageToReleaseFile(setupFilePath);
                //Copy installer as setup.exe
                CopyAsSetup(setupFilePath, releaseEntry);
                return 0;
            }
        }

        private void CopyToOutputFolder(string setupFilePath)
        {
            if (setupFilePath == null) throw new ArgumentNullException(nameof(setupFilePath));
            var setupFile = Path.GetFileName(setupFilePath);
            if (string.IsNullOrEmpty(setupFile)) throw new ArgumentException();
            if (!File.Exists(setupFilePath)) throw new FileNotFoundException(setupFile);
            if (!Directory.Exists(OutputDir)) Directory.CreateDirectory(OutputDir);
            if (!string.IsNullOrEmpty(ChangeLog))
            {
                var changeLogFilename = Path.GetFileName(ChangeLog);

                if (!File.Exists(ChangeLog)) throw new FileNotFoundException(ChangeLog);
                File.Copy(ChangeLog, Path.Combine(OutputDir, changeLogFilename), true);
            }
            File.Copy(setupFilePath, Path.Combine(OutputDir, setupFile), true);
        }


        private string ModifyAppStub()
        {
            var baseFile = Path.Combine(BaseDirectory, "contrib", Constants.ExecutionStub);
            var targetFile = Path.Combine(_tempDir.Path, Constants.ExecutionStub);
            File.Copy(baseFile, targetFile);
            Utils.CopyResources(ProgramFile, targetFile);
            Utils.SignExecutable(CertificatePath, CertificatePassword, targetFile);
            return targetFile;
        }

        private string CreateConfigFile()
        {
            var pathToFile = Path.Combine(_tempDir.Path, Constants.KattekerConfig);
            var kattekerConfig = new KattekerConfig
            {
                Publish = PublishDir ?? OutputDir,
                Changelog = Path.GetFileName(ChangeLog),
                Channel = Channel
            };
            kattekerConfig.WriteToFile(pathToFile);
            return pathToFile;
        }

        private static string GenerateFilename(string name, string version, string extension, bool isDelta = false) => $"{name}-{version}-{(isDelta ? "delta" : "full")}.{extension}";


        private string GenerateNsisTemplate(IEnumerable<PhysicalFile> additionalFiles)
        {
            try
            {
                var assemblyName = Path.GetFileNameWithoutExtension(_assemblyFileInfo.FileInfo.Name);
                var outFile = Path.Combine(_tempDir.Path, GenerateFilename(assemblyName, _assemblyFileInfo.AssemblyVersion.ToString(), "nsi"));
                var setupTemplate = new SetupTemplate
                {
                    Executable = _assemblyFileInfo.FileInfo.Name,
                    AssemblyName = assemblyName,
                    AppName = _assemblyFileInfo.ProductName,
                    CompanyName = _assemblyFileInfo.Company,
                    Description = _assemblyFileInfo.Description,
                    IconPath = _assemblyFileInfo.AssemblyIconPath,
                    Version = _assemblyFileInfo.AssemblyVersion,
                    HelpUrl = "http://example.org", //Not used at the moment.
                    UserInterface = Path.Combine(BaseDirectory, "contrib", "LoadingBar_Icon.exe"),
                    UninstallIcon = Path.Combine(BaseDirectory, "contrib", "uninstall.ico"),
                    OutFile = Path.GetFileName(Path.ChangeExtension(outFile, "exe")),
                    ReleaseChannel = Channel
                };
                var path = Path.GetDirectoryName(ProgramFile) ?? string.Empty;
                setupTemplate.Files.AddRange(additionalFiles);
                var files = Utils.EnumerateFiles(path, Filter).ToArray();
                setupTemplate.InstallSize = (long)Math.Floor(files.Sum(x => x.Length) / 1024f);
                setupTemplate.Files.AddRange(files.Select(x => new PhysicalFile(x.FullName, x.FullName.Replace(path, $"app-{_assemblyFileInfo.AssemblyVersion}"))));
                var transformText = setupTemplate.TransformText();
                File.WriteAllText(outFile, transformText);
                return outFile;
            }
            catch (Exception e)
            {
                Log.WriteErrorLine(e.Message);
                throw;
            }
        }

        private static string CompileSetupScript(string templateFile)
        {
            if (!File.Exists(MakeNsis)) throw new FileNotFoundException("NSIS not installed properly.");
            var exitCode = Utils.ExecuteProcess(MakeNsis, $"\"{templateFile}\"");

            if (exitCode != 0) throw new Exception($"{Path.GetFileName(MakeNsis)} has exited with exit code: {exitCode}");
            return Path.ChangeExtension(templateFile, "exe");
        }

        private ReleaseEntry AddPackageToReleaseFile(string setupFilePath)
        {
            try
            {
                var result = _releases.Add(setupFilePath, _assemblyFileInfo.AssemblyVersion);
                _releases.WriteReleaseFile();
                return result;
            }
            catch (Exception e)
            {
                Log.WriteErrorLine(e.Message);
                throw;
            }
        }

        private void CopyAsSetup(string setupFilePath, ReleaseEntry releaseEntry)
        {
            if (!_releases.IsLatestEntry(releaseEntry)) return;
            var target = Path.Combine(OutputDir, Constants.SetupFile);
            File.Delete(target);
            File.Copy(setupFilePath, target);
        }
    }
}