using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Windows.Forms;
using Ameliorated.ConsoleUtils;
using Microsoft.Dism;

// Asks user to select Windows installation media, and mounts it if applicable
// Returns path to where it's mounted

namespace amecs.Misc
{
    public static class SelectWindowsImage
    {
        private static string _fileViolationTest;
        private static bool CheckFileViolation(string inputFile)
        {
            try
            {
                _fileViolationTest = inputFile;
            }
            catch (SecurityException e)
            {
                Console.WriteLine();
                ConsoleTUI.OpenFrame.Close("Security exception: " + e.Message, ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                return true;
            }

            return false;
        }
        
        public static string GetWindowsVersion(float majorMinor, int isoBuild)
        {
            return (majorMinor, isoBuild) switch
            {
                (6, _) => "Windows Vista",
                (6.1f, _) => "Windows 7",
                (6.2f, _) => "Windows 8",
                (6.3f, _) => "Windows 8.1",
                (10, var a) when a < 19041 => "Windows 10 (Old)",
                (10, var a) when a >= 22000 => "Windows 11",
                (10, _) => "Windows 10",
                _ => "Unknown"
            };
        }
        
        public static bool DismountIso(string imagePath)
        {
            var startInfo = new ProcessStartInfo
            {
                CreateNoWindow = false,
                UseShellExecute = false,
                FileName = "PowerShell.exe",
                WindowStyle = ProcessWindowStyle.Hidden,
                Arguments = $"-NoP -C \"Dismount-DiskImage '{imagePath}'\"",
                RedirectStandardOutput = true
            };

            var proc = Process.Start(startInfo);
            if (proc == null) return false;
            proc.WaitForExit();
            return true;
        }
        
        private static string _mountedPath;
        private static string _isoPath;
        private static string _isoWinVer;
        private static int _isoBuild;
        
        /// <summary>
        /// Asks user to select Windows installation media, mounts it if applicable, and checks its version
        /// </summary>
        /// <param name="winVersionsMustMatch">If true when ISO and host versions mismatch, prompts user that things can break if they continue</param>
        /// <param name="isoBuildMustBeReturned">If true and the ISO build can't be retrieved, prompts a user with an error</param>
        public static (
            string MountedPath, string IsoPath, string Winver, int? Build, bool? VersionsMatch
            ) GetMediaPath(bool winVersionsMustMatch = false, bool isoBuildMustBeReturned = false)
        {
            var error = ((string)null, "none", (string)null, (int?)null, (bool?)null);
            var choice =
                new ChoicePrompt { Text = "To continue, Windows installation media is needed.\r\nDo you have a Windows USB instead of an ISO file? (Y/N): " }.Start();
            if (!choice.HasValue) return error;
            
            // Folder/drive chosen
            var usingFolder = choice == 0;
            if (usingFolder)
            {
                var dlg = new FolderPicker
                {
                    InputPath = Globals.UserFolder
                };

                if (dlg.ShowDialog(IntPtr.Zero).GetValueOrDefault())
                {
                    if (CheckFileViolation(dlg.ResultPath))
                        return error;
                    
                    _mountedPath = dlg.ResultPath;
                }                     
                else
                {
                    Console.WriteLine();
                    ConsoleTUI.OpenFrame.Close("\r\nYou must select a folder or drive containing Windows installation media.",
                        new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                    return error;
                }
            }
            else
            {
                // Mounting the ISO
                var dialog = new OpenFileDialog();
                dialog.Filter = "ISO Files (*.ISO)| *.ISO";
                dialog.Multiselect = false;
                dialog.InitialDirectory = Globals.UserFolder;

                var window = new NativeWindow();
                window.AssignHandle(Process.GetCurrentProcess().MainWindowHandle);
                if (dialog.ShowDialog(window) == DialogResult.OK)
                {
                    _isoPath = dialog.FileName;
                    if (CheckFileViolation(_isoPath)) return error;
                    Console.WriteLine();
                    ConsoleTUI.OpenFrame.WriteCentered("\r\nMounting ISO");
                }
                else
                {
                    Console.WriteLine();
                    ConsoleTUI.OpenFrame.Close("\r\nYou must select an ISO.",
                        new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                    return error;
                }

                using (new ConsoleUtils.LoadingIndicator(true))
                {
                    var startInfo = new ProcessStartInfo
                    {
                        CreateNoWindow = false,
                        UseShellExecute = false,
                        FileName = "PowerShell.exe",
                        WindowStyle = ProcessWindowStyle.Hidden,
                        Arguments = $"-NoP -C \"(Mount-DiskImage '{_isoPath}' -PassThru | Get-Volume).DriveLetter + ':\'\"",
                        RedirectStandardOutput = true
                    };

                    var proc = Process.Start(startInfo);
                    if (proc == null) return error;
                    proc.WaitForExit();

                    _mountedPath = proc.StandardOutput.ReadLine();
                }   
            }                        
            
            // Check WIM version
            var wimOrEsdPath = new[] { $@"{_mountedPath}\sources\install.esd", $@"{_mountedPath}\sources\install.wim" }.FirstOrDefault(File.Exists);
            if (!string.IsNullOrEmpty(wimOrEsdPath))
            {
                try
                {
                    DismApi.Initialize(DismLogLevel.LogErrors);
                    
                    string previousIndexVersion = null;
                    string isoFullVersion = null;
                    var multiVersion = false;
                    
                    var imageInfos = DismApi.GetImageInfo(wimOrEsdPath);
                    foreach (var imageInfo in imageInfos)
                    {
                        isoFullVersion = imageInfo.ProductVersion.ToString();
                        if (isoFullVersion != previousIndexVersion && previousIndexVersion != null)
                        {
                            // If it's multi-version, WinVer will be "Unknown" as well
                            multiVersion = true;
                            isoFullVersion = "0.0.0.0";
                            break;
                        }
                        previousIndexVersion = isoFullVersion;
                    }

                    switch (multiVersion)
                    {
                        case true when isoBuildMustBeReturned:
                            ConsoleTUI.OpenFrame.Close(
                                "Multiple Windows versions were found in the Windows image, can't determine which Windows build it is. Please use an unmodified Windows ISO.",
                                ConsoleColor.Red, Console.BackgroundColor,
                                new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                            return error;
                        case true when winVersionsMustMatch:
                            ConsoleTUI.OpenFrame.Close(
                                "Multiple Windows versions were found in the Windows image, can't determine which Windows build it is. If your Windows version doesn't match the ISO, there will be problems.",
                                ConsoleColor.Red, Console.BackgroundColor,
                                new ChoicePrompt { AnyKey = true, Text = "Press any key to continue anyways: " });
                        
                            Program.Frame.Clear();
                            ConsoleTUI.OpenFrame.WriteCentered("\r\nContinuing without version check...\r\n");
                            break;
                    }

                    var buildSplit = isoFullVersion.Split('.');
                    _isoBuild = int.Parse(buildSplit[2]);
                    _isoWinVer = GetWindowsVersion(float.Parse($"{buildSplit[0]}.{buildSplit[1]}"), _isoBuild);
                }
                catch (Exception e)
                {
                    Console.WriteLine();
                    ConsoleTUI.OpenFrame.Close(
                        "Error checking ISO version: " + e.Message.TrimEnd('\n').TrimEnd('\r'),
                        ConsoleColor.Red, Console.BackgroundColor,
                        new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                    return error;
                }
                finally
                {
                    try
                    {
                        DismApi.Shutdown();
                    }
                    catch
                    {
                        // do nothing
                    }
                }
                
                // Check the current OS version
                var hostVersion = Environment.OSVersion.Version;
                var hostWinver = GetWindowsVersion(float.Parse($"{hostVersion.Major}.{hostVersion.Minor}"), hostVersion.Build);
                
                // If it all matches & winVersionsMustMatch
                if (hostWinver == _isoWinVer) return (_mountedPath, _isoPath, _isoWinVer, _isoBuild, true);
                // If ISO version doesn't match host version & winVersionsMustMatch
                if (hostWinver != _isoWinVer && winVersionsMustMatch)
                {
                    if (!string.IsNullOrEmpty(_isoPath)) DismountIso(_isoPath);
                    ConsoleTUI.OpenFrame.Close(
                        $"You're on {hostWinver}, but the selected image is {_isoWinVer}. You can only use an ISO that matches your Windows version.",
                        ConsoleColor.Red, Console.BackgroundColor,
                        new ChoicePrompt { AnyKey = true, Text = "Press any key to return to the Menu: " });
                    return error;
                }
                
                // If ISO version doesn't match host version, and winVersionsMustMatch is true 
                if (hostWinver != _isoWinVer) return (_mountedPath, _isoPath, _isoWinVer, _isoBuild, false);
            }

            var noWimText = isoBuildMustBeReturned
                ? "Press any key to return to the Menu"
                : "Press any key to continue anyways";
            
            ConsoleTUI.OpenFrame.Close(
                "No Windows installation image was found inside the selected Windows media. No version check can be done, things might break.",
                ConsoleColor.Red, Console.BackgroundColor,
                new ChoicePrompt { AnyKey = true, Text = $"{noWimText}: " });
            
            Program.Frame.Clear();
            ConsoleTUI.OpenFrame.WriteCentered("\r\nContinuing without version check\r\n");
            
            return isoBuildMustBeReturned ? error : (_mountedPath, _isoPath, null, null, null);
        }
    }
}