using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Jackett.Common.Models.Config;
using Jackett.Common.Services;
using Jackett.Common.Services.Interfaces;
using Jackett.Common.Utils;
using NLog;

namespace Jackett.Tray
{
    public partial class Main : Form
    {
        private readonly IProcessService processService;
        private readonly IServiceConfigService windowsService;
        private readonly ITrayLockService trayLockService;
        private readonly ISerializeService serializeService;
        private readonly IConfigurationService configurationService;
        private readonly ServerConfig serverConfig;
        private Process consoleProcess;
        private readonly Logger logger;
        private bool closeApplicationInitiated;

        public Main(string updatedVersion)
        {
            Hide();
            InitializeComponent();

            Opacity = 0;
            Enabled = false;
            WindowState = FormWindowState.Minimized;
            FormBorderStyle = FormBorderStyle.FixedToolWindow;

            var runtimeSettings = new RuntimeSettings()
            {
                CustomLogFileName = "TrayLog.txt"
            };

            LogManager.Configuration = LoggingSetup.GetLoggingConfiguration(runtimeSettings);
            logger = LogManager.GetCurrentClassLogger();

            logger.Info("Starting Jackett Tray " + EnvironmentUtil.JackettVersion());

            processService = new ProcessService(logger);
            windowsService = new WindowsServiceConfigService(processService, logger);
            trayLockService = new TrayLockService();
            serializeService = new SerializeService();
            configurationService = new ConfigurationService(serializeService, processService, logger, runtimeSettings);
            serverConfig = configurationService.BuildServerConfig(runtimeSettings);

            toolStripMenuItemAutoStart.Checked = AutoStart;
            toolStripMenuItemAutoStart.CheckedChanged += toolStripMenuItemAutoStart_CheckedChanged;

            notifyIcon1.DoubleClick += toolStripMenuItemWebUI_Click;
            toolStripMenuItemWebUI.Click += toolStripMenuItemWebUI_Click;
            toolStripMenuItemShutdown.Click += toolStripMenuItemShutdown_Click;

            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                toolStripMenuItemAutoStart.Visible = true;
            }

            if (!windowsService.ServiceExists())
            {
                // We are not installed as a service so just start the web server via JackettConsole and run from the tray.
                logger.Info("Starting server from tray");
                StartConsoleApplication();
            }

            updatedVersion = updatedVersion.Equals("yes", StringComparison.OrdinalIgnoreCase) ? EnvironmentUtil.JackettVersion() : updatedVersion;

            if (!string.IsNullOrWhiteSpace(updatedVersion))
            {
                notifyIcon1.BalloonTipTitle = "Jackett";
                notifyIcon1.BalloonTipText = $"Jackett has updated to version {updatedVersion}";
                notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
                notifyIcon1.ShowBalloonTip(10000);
                logger.Info($"Display balloon tip, updated to {updatedVersion}");
            }

            Task.Factory.StartNew(WaitForEvent);
        }

        private void WaitForEvent()
        {
            trayLockService.WaitForSignal();
            logger.Info("Received signal from tray lock service");

            if (windowsService.ServiceExists() && windowsService.ServiceRunning())
            {
                // We won't be able to start the tray app up again from the updater, as when running via a windows
                // service there is no interaction with the desktop.

                var scriptPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "jackett_launcher.bat");
                var startInfo = new ProcessStartInfo()
                {
                    Arguments = $"/c \"{scriptPath}\"",
                    FileName = "cmd.exe",
                    UseShellExecute = true,
                    CreateNoWindow = true,
                    WindowStyle = ProcessWindowStyle.Hidden
                };

                logger.Info($"Starting launcher script as Jackett is running as a Windows service: {startInfo.FileName} {startInfo.Arguments}");
                Process.Start(startInfo);
            }

            CloseTrayApplication();
        }

        private void toolStripMenuItemWebUI_Click(object sender, EventArgs e)
        {
            var psi = new ProcessStartInfo
            {
                FileName = "http://127.0.0.1:" + serverConfig.Port,
                UseShellExecute = true
            };

            Process.Start(psi);
        }

        private void toolStripMenuItemShutdown_Click(object sender, EventArgs e) => CloseTrayApplication();

        private void toolStripMenuItemAutoStart_CheckedChanged(object sender, EventArgs e) => AutoStart = toolStripMenuItemAutoStart.Checked;

        private string ProgramTitle => Assembly.GetExecutingAssembly().GetName().Name;

        private bool AutoStart
        {
            get => File.Exists(ShortcutPath);
            set
            {
                if (value && !AutoStart)
                {
                    CreateShortcut();
                }
                else if (!value && AutoStart)
                {
                    File.Delete(ShortcutPath);
                }
            }
        }

        public string ShortcutPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Jackett.lnk");

        private void CreateShortcut()
        {
            var shellLink = (IShellLink)new ShellLink();
            shellLink.SetDescription("Jackett");
            shellLink.SetPath(Process.GetCurrentProcess().MainModule.FileName);
            shellLink.SetWorkingDirectory(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
            var persistFile = (IPersistFile)shellLink;
            persistFile.Save(ShortcutPath, false);
        }

        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            if (windowsService.ServiceExists())
            {
                backgroundMenuItem.Visible = true;
                serviceControlMenuItem.Visible = true;
                toolStripSeparator1.Visible = true;
                toolStripSeparator2.Visible = true;

                if (windowsService.ServiceRunning())
                {
                    serviceControlMenuItem.Text = "Stop background service";
                    backgroundMenuItem.Text = "Jackett is running as a background service";
                    toolStripMenuItemWebUI.Enabled = true;
                }
                else
                {
                    serviceControlMenuItem.Text = "Start background service";
                    backgroundMenuItem.Text = "Jackett will run as a background service";
                    toolStripMenuItemWebUI.Enabled = false;
                }

                toolStripMenuItemShutdown.Text = "Close tray icon";
            }
            else
            {
                backgroundMenuItem.Visible = false;
                serviceControlMenuItem.Visible = false;
                toolStripSeparator1.Visible = false;
                toolStripSeparator2.Visible = false;
                toolStripMenuItemShutdown.Text = "Shutdown";
            }
        }

        private void serviceControlMenuItem_Click(object sender, EventArgs e)
        {
            var consolePath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "JackettConsole.exe");

            if (windowsService.ServiceRunning())
            {
                if (ServerUtil.IsUserAdministrator())
                {
                    windowsService.Stop();
                }
                else
                {
                    try
                    {
                        processService.StartProcessAndLog(consolePath, "--Stop", true);
                    }
                    catch
                    {
                        MessageBox.Show("Failed to get admin rights to stop the service.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            else
            {
                if (ServerUtil.IsUserAdministrator())
                {
                    windowsService.Start();
                }
                else
                {
                    try
                    {
                        processService.StartProcessAndLog(consolePath, "--Start", true);
                    }
                    catch
                    {
                        MessageBox.Show("Failed to get admin rights to start the service.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }

        private void CloseTrayApplication()
        {
            closeApplicationInitiated = true;

            logger.Info("Close of tray application initiated");

            //Clears notify icon, otherwise icon will still appear on taskbar until you hover the mouse over
            notifyIcon1.Icon = null;
            notifyIcon1.Dispose();
            Application.DoEvents();

            if (consoleProcess != null && !consoleProcess.HasExited)
            {
                consoleProcess.StandardInput.Close();
                System.Threading.Thread.Sleep(1000);
                if (consoleProcess != null && !consoleProcess.HasExited)
                {
                    consoleProcess.Kill();
                }
            }

            Application.Exit();
        }

        private void StartConsoleApplication()
        {
            var exePath = Path.Combine(EnvironmentUtil.JackettInstallationPath(), "JackettConsole.exe");

            var startInfo = new ProcessStartInfo()
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                FileName = exePath,
                RedirectStandardInput = true,
                RedirectStandardError = true
            };

            consoleProcess = Process.Start(startInfo);
            consoleProcess.EnableRaisingEvents = true;
            consoleProcess.Exited += ProcessExited;
            consoleProcess.ErrorDataReceived += ProcessErrorDataReceived;
        }

        private void ProcessErrorDataReceived(object sender, DataReceivedEventArgs e) => logger.Error(e.Data);

        private void ProcessExited(object sender, EventArgs e)
        {
            if (!closeApplicationInitiated)
            {
                logger.Info("Tray icon not responsible for process exit");
                CloseTrayApplication();
            }
        }

        //Used for creating the Windows startup shortcut
        [ComImport]
        [Guid("00021401-0000-0000-C000-000000000046")]
        internal class ShellLink
        {
        }

        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("000214F9-0000-0000-C000-000000000046")]
        internal interface IShellLink
        {
            void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);
            void GetIDList(out IntPtr ppidl);
            void SetIDList(IntPtr pidl);
            void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
            void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
            void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
            void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
            void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
            void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
            void GetHotkey(out short pwHotkey);
            void SetHotkey(short wHotkey);
            void GetShowCmd(out int piShowCmd);
            void SetShowCmd(int iShowCmd);
            void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
            void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
            void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
            void Resolve(IntPtr hwnd, int fFlags);
            void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
        }
    }
}
