using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using TrustedUninstaller.Shared.Tasks;
using YamlDotNet.Serialization;

namespace TrustedUninstaller.Shared.Actions
{
    class TaskKillAction : ITaskAction
    {
        [DllImport("kernel32.dll", SetLastError=true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
        
        [YamlMember(typeof(string), Alias = "name")]
        public string? ProcessName { get; set; }
        
        [YamlMember(typeof(string), Alias = "pathContains")]
        public string? PathContains { get; set; }
        
        [YamlMember(typeof(string), Alias = "weight")]
        public int ProgressWeight { get; set; } = 2;
        public int GetProgressWeight() => ProgressWeight;

        private bool InProgress { get; set; }
        public void ResetProgress() => InProgress = false;
        
        public int? ProcessID { get; set; }
        
        public string ErrorString()
        {
            string text = $"TaskKillAction failed to kill processes matching '{ProcessName}'.";

            try
            {
                var processes = GetProcess().Select(process => process.ProcessName).Distinct().ToList();
                if (processes.Count > 1)
                {
                    text = $"TaskKillAction failed to kill processes:";
                    foreach (var process in processes)
                    {
                        text += "|NEWLINE|" + process;
                    }
                }
                else if (processes.Count == 1) text = $"TaskKillAction failed to kill process {processes[0]}.";
            } catch (Exception) { }

            return text;
        }

        public UninstallTaskStatus GetStatus()
        {
            if (InProgress)
            {
                return UninstallTaskStatus.InProgress;
            }

            List<Process> processToTerminate = new List<Process>();
            if (ProcessID.HasValue)
            {
                try { processToTerminate.Add(Process.GetProcessById((int)ProcessID)); } catch (Exception) { } 
            }
            else
            {
                processToTerminate = GetProcess().ToList();
            }

            return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed;
        }

        private IEnumerable<Process> GetProcess()
        {
            if (ProcessName == null) return new List<Process>();
            
            if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses()
                .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
            if (ProcessName.EndsWith("*")) return Process.GetProcesses()
                .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
            if (ProcessName.StartsWith("*")) return Process.GetProcesses()
                .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));

            return Process.GetProcessesByName(ProcessName);
        }
        [DllImport("kernel32.dll", SetLastError=true)]
        static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical);
        
        private readonly string[] RegexNoKill = { "lsass", "csrss", "winlogon", "TrustedUninstaller\\.CLI", "dwm", "conhost", "ame.?wizard", "ame.?assassin" };
        // These processes give access denied errors when getting their handle for IsProcessCritical.
        // TODO: Investigate how to properly acquire permissions.
        private readonly string[] RegexNotCritical = { "SecurityHealthService", "wscsvc", "MsMpEng", "SgrmBroker" };
        public async Task<bool> RunTask()
        {
            InProgress = true;
            
            if (string.IsNullOrEmpty(ProcessName) && ProcessID.HasValue)
            {
                Console.WriteLine($"Killing process with PID '{ProcessID.Value}'...");
            }
            else
            {
                if (ProcessName != null && RegexNoKill.Any(regex => Regex.Match(ProcessName, regex, RegexOptions.IgnoreCase).Success))
                {
                    Console.WriteLine($"Skipping {ProcessName}...");
                    return false;
                }
                
                Console.WriteLine($"Killing processes matching '{ProcessName}'...");
            }
            var cmdAction = new CmdAction();
            
            if (ProcessName != null)
            {
                //If the service is svchost, we stop the service instead of killing it.
                if (ProcessName.Contains("svchost"))
                {
                    // bool serviceFound = false;
                    try
                    {
                        using var search = new ManagementObjectSearcher($"select * from Win32_Service where ProcessId = '{ProcessID}'");

                        foreach (ManagementObject queryObj in search.Get())
                        {
                            var serviceName = (string)queryObj["Name"]; // Access service name  
                            
                            var stopServ = new ServiceAction()
                            {
                                ServiceName = serviceName,
                                Operation = ServiceOperation.Stop

                            };
                            await stopServ.RunTask();
                        }
                    }
                    catch (NullReferenceException e)
                    {
                        Console.WriteLine($"A service with PID: {ProcessID} could not be found.");
                        ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Could not find service with PID {ProcessID}.");
                    }


/*                    foreach (var serv in servicesToDelete)
                    {
                        //The ID can only be associated with one of the services, there's no need to loop through
                        //them all if we already found the service.
                        if (serviceFound)
                        {
                            break;
                        }

                        try
                        {
                            using var search = new ManagementObjectSearcher($"select ProcessId from Win32_Service where Name = '{serv}'").Get();
                            var servID = (uint)search.OfType<ManagementObject>().FirstOrDefault()["ProcessID"];

                            if (servID == ProcessID)
                            {
                                serviceFound = true;



                            }
                            search.Dispose();
                        }
                        catch (Exception e)
                        {
                            var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get();
                            var servName = search.OfType<ManagementObject>().FirstOrDefault()["Name"];
                            Console.WriteLine($"Could not find {servName} but PID {ProcessID} still exists.");
                            ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Exception Type: {e.GetType()}");
                            return false;
                        }
                    }*/
                    //None of the services listed, we shouldn't kill svchost.
/*                    if (!serviceFound)
                    {
                        var search = new ManagementObjectSearcher($"select Name from Win32_Service where ProcessID = '{ProcessID}'").Get();
                        var servName = search.OfType<ManagementObject>().FirstOrDefault()["Name"];
                        Console.WriteLine($"A critical system process \"{servName}\" with PID {ProcessID} caused the Wizard to fail.");
                        await WinUtil.UninstallDriver();
                        Environment.Exit(-1);
                        return false;
                    }*/

                    await Task.Delay(100);

                    InProgress = false;
                    return true;
                }

                if (PathContains != null && !ProcessID.HasValue)
                {
                    var processes = GetProcess().ToList();
                    if (processes.Count > 0) Console.WriteLine("Processes:");

                    foreach (var process in processes.Where(x => x.MainModule.FileName.Contains(PathContains)))
                    {
                        Console.WriteLine(process.ProcessName + " - " + process.Id);

                        if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) {
                            bool isCritical = false;
                            IsProcessCritical(process.Handle, ref isCritical);
                            if (isCritical)
                            {
                                Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
                                continue;
                            }
                        }
                        
                        cmdAction.Command = Environment.Is64BitOperatingSystem ?
                            $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate" :
                            $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate";
                        await cmdAction.RunTask();
                        
                        int i = 0;
                        while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName))
                        {
                            await Task.Delay(300);
                            i++;
                        }
                        if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error");
                    }
                    InProgress = false;
                    return true;
                }
            }
            
            if (ProcessID.HasValue)
            {
                if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var process = Process.GetProcessById(ProcessID.Value);
                        TerminateProcess(process.Handle, 1);
                    } catch (Exception)
                    {
                        cmdAction.Command = Environment.Is64BitOperatingSystem ?
                            $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" :
                            $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate";
                        await cmdAction.RunTask();
                    }
                }
                else
                {
                    var process = Process.GetProcessById(ProcessID.Value);

                    if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success))
                    {
                        bool isCritical = false;
                        IsProcessCritical(process.Handle, ref isCritical);
                        if (isCritical)
                        {
                            Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
                            return false;
                        }
                    }
                    cmdAction.Command = Environment.Is64BitOperatingSystem ?
                        $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate" :
                        $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID} -caction terminate";
                    await cmdAction.RunTask();
                }

                await Task.Delay(100);
            }
            else
            {
                var processes = GetProcess().ToList();
                if (processes.Count > 0) Console.WriteLine("Processes:");

                foreach (var process in processes)
                {
                    Console.WriteLine(process.ProcessName + " - " + process.Id);
                    
                    cmdAction.Command = Environment.Is64BitOperatingSystem ?
                        $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate" :
                        $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.ProcessName}.exe -caction terminate";
                    if (process.ProcessName == "explorer") TerminateProcess(process.Handle, 1);
                    else
                    {
                        if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success))
                        {
                            bool isCritical = false;
                            IsProcessCritical(process.Handle, ref isCritical);
                            if (isCritical)
                            {
                                Console.WriteLine($"{process.ProcessName} is a critical process, skipping...");
                                continue;
                            }
                        }

                        await cmdAction.RunTask();
                    }

                    int i = 0;

                    while (i <= 15 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName))
                    {
                        await Task.Delay(300);
                        i++;
                    }
                    if (i >= 15) ErrorLogger.WriteToErrorLog($"Task kill timeout exceeded.", Environment.StackTrace, "TaskKillAction Error");
                }
            }
            
            InProgress = false;
            return true;
        }
    }
}
