// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using ManagedCommon;
using Microsoft.CmdPal.Ext.WindowsServices.Commands;
using Microsoft.CmdPal.Ext.WindowsServices.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Win32;
using Windows.System;

namespace Microsoft.CmdPal.Ext.WindowsServices.Helpers;

public static class ServiceHelper
{
    public static IEnumerable<ListItem> Search(string search, string filterId)
    {
        var services = ServiceController.GetServices().OrderBy(s => s.DisplayName);
        IEnumerable<ServiceController> serviceList = [];

        if (search.StartsWith(Resources.wox_plugin_service_status + ":", StringComparison.CurrentCultureIgnoreCase))
        {
            // allows queries like 'status:running'
            serviceList = services.Where(s => GetLocalizedStatus(s.Status).Contains(search.Split(':')[1], StringComparison.CurrentCultureIgnoreCase));
        }
        else if (search.StartsWith(Resources.wox_plugin_service_startup + ":", StringComparison.CurrentCultureIgnoreCase))
        {
            // allows queries like 'startup:automatic'
            serviceList = services.Where(s => GetLocalizedStartType(s.StartType, s.ServiceName).Contains(search.Split(':')[1], StringComparison.CurrentCultureIgnoreCase));
        }
        else
        {
            // To show 'starts with' results first, we split the search into two steps and then concatenating the lists.
            var servicesStartsWith = services
                .Where(s => s.DisplayName.StartsWith(search, StringComparison.OrdinalIgnoreCase) || s.ServiceName.StartsWith(search, StringComparison.OrdinalIgnoreCase));
            var servicesContains = services.Except(servicesStartsWith)
                .Where(s => s.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase) || s.ServiceName.Contains(search, StringComparison.OrdinalIgnoreCase));
            serviceList = servicesStartsWith.Concat(servicesContains);
        }

        switch (filterId)
        {
            case "running":
                serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Running);
                break;
            case "stopped":
                serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Stopped);
                break;
            case "paused":
                serviceList = serviceList.Where(w => w.Status == ServiceControllerStatus.Paused);
                break;
            case "all":
                break;
        }

        var result = serviceList.Select(s =>
        {
            var serviceResult = ServiceResult.CreateServiceController(s);
            if (serviceResult is null)
            {
                return null;
            }

            ServiceCommand serviceCommand;
            CommandContextItem[] moreCommands;
            if (serviceResult.IsRunning)
            {
                serviceCommand = new ServiceCommand(serviceResult, Action.Stop);
                moreCommands = [
                    new CommandContextItem(new RestartServiceCommand(serviceResult)),
                    new CommandContextItem(new OpenServicesCommand(serviceResult))
                    {
                        RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, (int)VirtualKey.O, 0),
                    },
                ];
            }
            else
            {
                serviceCommand = new ServiceCommand(serviceResult, Action.Start);
                moreCommands = [
                    new CommandContextItem(new OpenServicesCommand(serviceResult)),
                ];
            }

            IconInfo icon = Icons.PlayIcon;
            switch (s.Status)
            {
                case ServiceControllerStatus.Stopped:
                    icon = Icons.StopIcon;
                    break;
                case ServiceControllerStatus.Running:
                    break;
                case ServiceControllerStatus.Paused:
                    icon = Icons.PauseIcon;
                    break;
            }

            return new ListItem(serviceCommand)
            {
                Title = s.DisplayName,
                Subtitle = ServiceHelper.GetResultSubTitle(s),
                MoreCommands = moreCommands,
                Icon = icon,

                // TODO GH #78 we need to improve the icon story
                // TODO GH #126 investigate tooltip story
                // ToolTipData = new ToolTipData(serviceResult.DisplayName, serviceResult.ServiceName),
                // IcoPath = icoPath,
            };
        }).Where(s => s is not null);

        return result;
    }

    // TODO GH #118 IPublicAPI contextAPI isn't used anymore, but we need equivalent ways to show notifications and status
    public static void ChangeStatus(ServiceResult serviceResult, Action action)
    {
        ArgumentNullException.ThrowIfNull(serviceResult);

        // ArgumentNullException.ThrowIfNull(contextAPI);
        try
        {
            var info = new ProcessStartInfo
            {
                FileName = "net",
                Verb = "runas",
                UseShellExecute = true,
                WindowStyle = ProcessWindowStyle.Hidden,
            };

            if (action == Action.Start)
            {
                info.Arguments = $"start \"{serviceResult.ServiceName}\"";
            }
            else if (action == Action.Stop)
            {
                info.Arguments = $"stop \"{serviceResult.ServiceName}\"";
            }
            else if (action == Action.Restart)
            {
                info.FileName = "cmd";
                info.Arguments = $"/c net stop \"{serviceResult.ServiceName}\" && net start \"{serviceResult.ServiceName}\"";
            }

            var process = Process.Start(info);
            process.WaitForExit();
            var exitCode = process.ExitCode;

#pragma warning disable IDE0059, CS0168, SA1005
            if (exitCode == 0)
            {
                // TODO GH #118 feedback to users
                // contextAPI.ShowNotification(GetLocalizedMessage(action), serviceResult.DisplayName);
            }
            else
            {
                // TODO GH #108 We need to figure out some logging
                // contextAPI.ShowNotification(GetLocalizedErrorMessage(action), serviceResult.DisplayName);
                // Log.Error($"The command returned {exitCode}", MethodBase.GetCurrentMethod().DeclaringType);
                Logger.LogError($"The command returned {exitCode}");
            }
        }
        catch (Win32Exception ex)
        {
            // TODO GH #108 We need to figure out some logging
            // Log.Error(ex.Message, MethodBase.GetCurrentMethod().DeclaringType);
            Logger.LogError($"Failed to change service '{serviceResult.DisplayName}' status to {action}: {ex.Message}");
        }
    }
#pragma warning restore IDE0059, CS0168, SA1005

    public static void OpenServices()
    {
        try
        {
            var startInfo = new System.Diagnostics.ProcessStartInfo
            {
                FileName = "services.msc",
                UseShellExecute = true,
            };

            System.Diagnostics.Process.Start(startInfo);
        }
#pragma warning disable IDE0059, CS0168, SA1005
        catch (Exception ex)
        {
            // TODO GH #108 We need to figure out some logging
            Logger.LogError($"Failed to open services.msc: {ex.Message}");
        }
    }
#pragma warning restore IDE0059, CS0168, SA1005

    private static string GetResultSubTitle(ServiceController serviceController)
    {
        ArgumentNullException.ThrowIfNull(serviceController);
        return $"{Resources.wox_plugin_service_status}: {GetLocalizedStatus(serviceController.Status)} - {Resources.wox_plugin_service_startup}: {GetLocalizedStartType(serviceController.StartType, serviceController.ServiceName)} - {Resources.wox_plugin_service_name}: {serviceController.ServiceName}";
    }

    private static string GetLocalizedStatus(ServiceControllerStatus status)
    {
        if (status == ServiceControllerStatus.Stopped)
        {
            return Resources.wox_plugin_service_stopped;
        }
        else if (status == ServiceControllerStatus.StartPending)
        {
            return Resources.wox_plugin_service_start_pending;
        }
        else if (status == ServiceControllerStatus.StopPending)
        {
            return Resources.wox_plugin_service_stop_pending;
        }
        else if (status == ServiceControllerStatus.Running)
        {
            return Resources.wox_plugin_service_running;
        }
        else
        {
            return status == ServiceControllerStatus.ContinuePending
                ? Resources.wox_plugin_service_continue_pending
                : status == ServiceControllerStatus.PausePending
                            ? Resources.wox_plugin_service_pause_pending
                            : status == ServiceControllerStatus.Paused ? Resources.wox_plugin_service_paused : status.ToString();
        }
    }

    private static string GetLocalizedStartType(ServiceStartMode startMode, string serviceName)
    {
        if (startMode == ServiceStartMode.Boot)
        {
            return Resources.wox_plugin_service_start_mode_boot;
        }
        else if (startMode == ServiceStartMode.System)
        {
            return Resources.wox_plugin_service_start_mode_system;
        }
        else
        {
            return startMode == ServiceStartMode.Automatic
                ? !IsDelayedStart(serviceName) ? Resources.wox_plugin_service_start_mode_automatic : Resources.wox_plugin_service_start_mode_automaticDelayed
                : startMode == ServiceStartMode.Manual
                            ? Resources.wox_plugin_service_start_mode_manual
                            : startMode == ServiceStartMode.Disabled ? Resources.wox_plugin_service_start_mode_disabled : startMode.ToString();
        }
    }

    private static string GetLocalizedMessage(Action action)
    {
        return action == Action.Start
            ? Resources.wox_plugin_service_started_notification
            : action == Action.Stop
                ? Resources.wox_plugin_service_stopped_notification
                : action == Action.Restart ? Resources.wox_plugin_service_restarted_notification : string.Empty;
    }

    private static string GetLocalizedErrorMessage(Action action)
    {
        return action == Action.Start
            ? Resources.wox_plugin_service_start_error_notification
            : action == Action.Stop
                ? Resources.wox_plugin_service_stop_error_notification
                : action == Action.Restart ? Resources.wox_plugin_service_restart_error_notification : string.Empty;
    }

    private static bool IsDelayedStart(string serviceName) => (int?)Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Services\" + serviceName, false)?.GetValue("DelayedAutostart", 0, RegistryValueOptions.None) == 1;
}
