using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DryIoc;
using Serilog;

namespace Artemis.UI.Linux;

public class ApplicationStateManager
{
    private readonly IContainer _container;
    private readonly ILogger _logger;
    private readonly IWindowService _windowService;


    // ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
    private Mutex? _artemisMutex;

    public ApplicationStateManager(IContainer container, string[] startupArguments)
    {
        _container = container;
        _logger = container.Resolve<ILogger>();
        _windowService = container.Resolve<IWindowService>();
        StartupArguments = startupArguments;

        Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
        Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
        Core.Utilities.UpdateRequested += UtilitiesOnUpdateRequested;

        // On OS shutdown dispose the IOC container just so device providers get a chance to clean up
        if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
            controlledApplicationLifetime.Exit += ControlledApplicationLifetimeOnExit;
    }

    public string[] StartupArguments { get; }

    public bool FocusExistingInstance()
    {
        _artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535d", out bool createdNew);
        if (createdNew)
            return false;

        return RemoteFocus();
    }

    public void DisplayException(Exception e)
    {
        try
        {
            _windowService.ShowExceptionDialog("An unhandled exception occured", e);
        }
        catch
        {
            // ignored, we tried
        }
    }

    private bool RemoteFocus()
    {
        // At this point we cannot read the database yet to retrieve the web server port.
        // Instead use the method external applications should use as well.
        if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
        {
            KillOtherInstances();
            return false;
        }

        string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
        using HttpClient client = new();
        try
        {
            CancellationTokenSource cts = new();
            cts.CancelAfter(2000);

            HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
            httpResponseMessage.EnsureSuccessStatusCode();
            return true;
        }
        catch (Exception)
        {
            KillOtherInstances();
            return false;
        }
    }

    private void KillOtherInstances()
    {
        // Kill everything else heh
        List<Process> processes = Process.GetProcessesByName("Artemis.UI").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
        foreach (Process process in processes)
        {
            try
            {
                process.Kill(true);
            }
            catch (Exception)
            {
                // ignored
            }
        }
    }

    private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e)
    {
        List<string> argsList = new();
        argsList.AddRange(StartupArguments);
        if (e.ExtraArgs != null)
            argsList.AddRange(e.ExtraArgs.Except(argsList));

        //TODO: start new instance with correct arguments

        if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
            Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
    }

    private void ControlledApplicationLifetimeOnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
    {
        _logger.Information("Application lifetime exiting, disposing container and friends");
        
        RunForcedShutdownIfEnabled();

        // Dispose plugins before disposing the IOC container because plugins might access services during dispose
        _container.Resolve<IPluginManagementService>().Dispose();
        _container.Dispose();
    }

    private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
    {
        RunForcedShutdownIfEnabled();

        if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
            Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
    }

    private void RunForcedShutdownIfEnabled()
    {
        if (StartupArguments.Contains("--disable-forced-shutdown"))
            return;

        //todo
    }
    
    private void UtilitiesOnUpdateRequested(object? sender, UpdateEventArgs e)
    {
        List<string> argsList = new(StartupArguments);
        if (e.Silent && !argsList.Contains("--minimized"))
            argsList.Add("--minimized");

        // Retain startup arguments after update by providing them to the script
        string script = Path.Combine(Constants.UpdatingFolder, "installing", "Scripts", "update.sh");
        string source = $"\"{Path.Combine(Constants.UpdatingFolder, "installing")}\"";
        string destination = $"\"{Constants.ApplicationFolder}\"";
        string args = argsList.Any() ? string.Join(' ', argsList) : "";

        RunScriptWithOutputFile(script, $"{source} {destination} {args}", Path.Combine(Constants.DataFolder, "update-log.txt"));

        // Lets try a graceful shutdown, the script will kill if needed
        if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
            Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
    }
    
    private static void RunScriptWithOutputFile(string script, string arguments, string outputFile)
    {
        ProcessStartInfo info = new()
        {
            Arguments = $"\"{script}\" {arguments} > \"{outputFile}\"",
            FileName = "/bin/bash",
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
        };
        Process.Start(info);
    }
}