using Katteker.Types;
using KattekerCreator.Exceptions;
using KattekerCreator.Types;
using McMaster.Extensions.CommandLineUtils;
using Semver;
using Serilog;
using System;
using System.IO;
using static KattekerCreator.Helper.Utils;
using Constants = Katteker.Utilities.Constants;

namespace KattekerCreator
{
    public sealed class Creator : CreatorArguments, IDisposable
    {
        private static readonly string MakeNsisPath = Nsis.Initialize();
        private static readonly string SevenZipPath = SevenZip.Initialize();
        private Releases _releases = new();
        private TemporaryDirectory _tempDir;

        void IDisposable.Dispose() => ((IDisposable)_tempDir).Dispose();

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

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

                //Acquire infos from Executable.
                Log.Information("Acquire program file properties");
                var assemblyFileInfo = new AssemblyFileInfo(ProgramFile, VersionOverride, _tempDir.Path);
                WriteAssemblyInfoToLog(assemblyFileInfo);

                var configFile = CreateConfigFile();

                //Create archive with program files
                var archive = CreateArchive(assemblyFileInfo.FilePath);
                WriteOutputSizeToLog(assemblyFileInfo.FilePath, archive);
                
                //Generate NSIS-Script
                var templateFile = GenerateNsisTemplate(assemblyFileInfo, configFile, archive, /*TODO*/ 0);
                
                //Start makensis.exe
                var setupFilePath = CompileSetupScript(templateFile);
                
                //Sign setup
                if (CertificatePath is not null && CertificatePassword is not null)
                {
                    SignExecutable(CertificatePath, CertificatePassword, setupFilePath, Verbose);
                }

                //Copy to Output-Folder
                CopyToOutputFolder(setupFilePath);
                //Create/Modify RELEASE File
                var releaseEntry = AddPackageToReleaseFile(setupFilePath, assemblyFileInfo.AssemblyVersion);
                //Copy installer as setup.exe
                CopyAsSetup(setupFilePath, releaseEntry);
                return 0;
            }
            catch (Exception e)
            {
                Log.Error(e, "Unhandled exception");
                return -1;
            }
        }

        private ReleaseEntry AddPackageToReleaseFile(string setupFilePath, SemVersion assemblyVersion)
        {
            if (setupFilePath is null) throw new ArgumentNullException(nameof(setupFilePath));
            if (assemblyVersion is null) throw new ArgumentNullException(nameof(assemblyVersion));

            try
            {
                var result = _releases.Add(setupFilePath, assemblyVersion);
                _releases.WriteReleaseFile();
                return result;
            }
            catch (Exception e)
            {
                Log.Error(e, "Add package to release file");
                throw;
            }
        }

        private string CompileSetupScript(string templateFile)
        {
            Log.Information("Compile NSIS script");

            if (!File.Exists(MakeNsisPath)) throw new FileNotFoundException("NSIS not installed properly.");
            var successExecuteProcess = ExecuteProcess(MakeNsisPath, $"\"{templateFile}\"", Verbose);

            return successExecuteProcess
                ? Path.ChangeExtension(templateFile, "exe")
                : throw new KattekerCreatorException("Compiling NSIS setup was not successful");
        }

        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);
        }

        private void CopyToOutputFolder(string setupFilePath)
        {
            Log.Information("Copy to output folder");

            if (setupFilePath is null) throw new ArgumentNullException(nameof(setupFilePath));
            var setupFile = Path.GetFileName(setupFilePath);
            if (string.IsNullOrEmpty(setupFile)) throw new KattekerCreatorException(nameof(setupFile));
            if (!File.Exists(setupFilePath)) throw new FileNotFoundException(setupFile);
            if (string.IsNullOrEmpty(OutputDir)) throw new KattekerCreatorException("Output directory is not defined");
            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 CreateArchive(string filesListPath)
        {
            var path = Path.Combine(filesListPath, "*");
            var archiveFilePath = Path.Combine(_tempDir.Path, SevenZip.ArchiveName);
            var exclusions = "-x!" + string.Join(" -x!", FileExclusions(Filter));
            var successExecuteProcess =
                ExecuteProcess(SevenZipPath, $"a -t7z {archiveFilePath} -mx {exclusions} -r \"{path}\"", Verbose);
            return successExecuteProcess
                ? archiveFilePath
                : throw new KattekerCreatorException("Archive could not be created");
        }

        private string CreateConfigFile()
        {
            Log.Information("Write configuration file");
            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 string GenerateNsisTemplate(AssemblyFileInfo assemblyFileInfo, string configFile,string archive, int installSize)
        {
            if (assemblyFileInfo is null) throw new ArgumentNullException(nameof(assemblyFileInfo));

            Log.Information("Write NSIS script");
            try
            {
                var onInstSuccessContent = GetContentOfFile(OnInstallationSucceededScriptPath);
                var onUnInstSuccessContent = GetContentOfFile(OnUnInstallationSucceededScriptPath);
                var assemblyName = assemblyFileInfo.FileNameWithoutExtension;
                var outFile = Path.Combine(_tempDir.Path, GenerateFilename(assemblyName, assemblyFileInfo.AssemblyVersion.ToString(), "nsi", Channel));
                var setupTemplate = new SetupTemplate
                {
                    Executable = assemblyFileInfo.FileName,
                    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,
                    OnInstSuccessContent = onInstSuccessContent,
                    OnUnInstSuccessContent = onUnInstSuccessContent,
                    WaitForProcessExe = Path.Combine(BaseDirectory, "contrib", "wait-for-process.exe"),
                    InstallSize = installSize,
                    AppPackage = archive,
                    KattekerConfig = configFile,
                };

                var transformText = setupTemplate.TransformText();
                File.WriteAllText(outFile, transformText);
                return outFile;
            }
            catch (Exception e)
            {
                Log.Error(e, "Error writing Nsis template");
                throw;
            }
        }
    }
}