using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;

namespace Moonlight.App.Services.Background;

public class MalwareBackgroundScanService
{
    private readonly EventSystem Event;
    private readonly IServiceScopeFactory ServiceScopeFactory;

    public bool IsRunning => !ScanTask?.IsCompleted ?? false;
    public bool ScanAllServers { get; set; }
    public readonly Dictionary<Server, MalwareScanResult> ScanResults;
    public string Status { get; private set; } = "N/A";

    private Task? ScanTask;

    public MalwareBackgroundScanService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
    {
        ServiceScopeFactory = serviceScopeFactory;
        Event = eventSystem;
        ScanResults = new();
    }

    public Task Start()
    {
        if (IsRunning)
            throw new DisplayException("Malware scan is already running");
        
        ScanTask = Task.Run(Run);
        
        return Task.CompletedTask;
    }

    private async Task Run()
    {
        // Clean results
        Status = "Clearing last results";
        await Event.Emit("malwareScan.status", IsRunning);

        lock (ScanResults)
            ScanResults.Clear();

        await Event.Emit("malwareScan.result");

        using var scope = ServiceScopeFactory.CreateScope();
        var serverRepo  = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
        var malwareScanService = scope.ServiceProvider.GetRequiredService<MalwareScanService>();
        
        Status = "Fetching servers to scan";
        await Event.Emit("malwareScan.status", IsRunning);
        
        Server[] servers;

        if (ScanAllServers)
            servers = serverRepo.Get().ToArray();
        else
            servers = await GetOnlineServers();
        
        // Perform scan

        int i = 1;
        foreach (var server in servers)
        {
            Status = $"[{i} / {servers.Length}] Scanning server {server.Name}";
            await Event.Emit("malwareScan.status", IsRunning);
            
            var result = await malwareScanService.Perform(server);

            if (result != null)
            {
                lock (ScanResults)
                {
                    ScanResults.Add(server, result);
                }
                
                await Event.Emit("malwareScan.result", server);
            }

            i++;
        }

        Task.Run(async () => // Because we use the task as the status indicator we need to notify the event system in a new task
        {
            await Task.Delay(TimeSpan.FromSeconds(5));
            await Event.Emit("malwareScan.status", IsRunning);
        });
    }

    private async Task<Server[]> GetOnlineServers()
    {
        using var scope = ServiceScopeFactory.CreateScope();
        
        // Load services from di scope
        var nodeRepo = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
        var serverRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
        var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();

        var nodes = nodeRepo.Get().ToArray();
        var containers = new List<Container>();

        // Fetch and summarize all running containers from all nodes
        Logger.Verbose("Fetching and summarizing all running containers from all nodes");
        
        Status = "Fetching and summarizing all running containers from all nodes";
        await Event.Emit("malwareScan.status", IsRunning);
        
        foreach (var node in nodes)
        {
            var metrics = await nodeService.GetDockerMetrics(node);

            foreach (var container in metrics.Containers)
            {
                containers.Add(container);
            }
        }

        var containerServerMapped = new Dictionary<Server, Container>();

        // Map all the containers to their corresponding server if existing
        Logger.Verbose("Mapping all the containers to their corresponding server if existing");
        
        Status = "Mapping all the containers to their corresponding server if existing";
        await Event.Emit("malwareScan.status", IsRunning);
        
        foreach (var container in containers)
        {
            if (Guid.TryParse(container.Name, out Guid uuid))
            {
                var server = serverRepo
                    .Get()
                    .FirstOrDefault(x => x.Uuid == uuid);
                
                if(server == null)
                    continue;
                
                containerServerMapped.Add(server, container);
            }
        }

        return containerServerMapped.Keys.ToArray();
    }
}