using IWS5.Importer.Abstractions.Interfaces;
using IWS5.Importer.Abstractions.Services;
using Serilog.Context;
using ILogger = Serilog.ILogger;

namespace IWS5.Importer.Worker.Processors;

/// <summary>
/// Processor responsible for handling the import directory,
/// validating files, and processing work items.
/// </summary>
public class FileImportProcessor
    : IFileImportProcessor
{
    private readonly string _importDir;
    private readonly string _copyErrorLocation;
    private readonly string _backupLocation;

    private readonly IFileHelper _fileHelper;
    private readonly IFileMover _fileValidation;
    private readonly IFolderMover _folderMover;
    private readonly IWorkItemProcessor _workItemProcessor;
    private readonly IProcessService _processService;
    private readonly ILogger _logService;

    private readonly int _maxDegreeOfParallelism;

    public FileImportProcessor(IGeneralImportSettingsProvider settingsProvider,
                             IFileHelper fileHelper,
                             IFileMover fileValidation,
                             IFolderMover folderMover,
                             IWorkItemProcessor workItemProcessor,
                             IProcessService processService,
                             ILogger logService)
    {
        var config = settingsProvider.Settings;
        _importDir = config.ImportDirs;
        _copyErrorLocation = config.CopyErrorLocation;
        _backupLocation = config.CopyLocation;

        int cpuPercent = config.CpuUsageInPercent;
        int totalCores = Environment.ProcessorCount;
        _maxDegreeOfParallelism = Math.Max(1, (int)Math.Ceiling(totalCores * (cpuPercent / 100.0)));

        _fileHelper = fileHelper;
        _fileValidation = fileValidation;
        _folderMover = folderMover;
        _workItemProcessor = workItemProcessor;
        _processService = processService;
        _logService = logService;
    }

    public async Task ProcessImportDirectory(CancellationToken stoppingToken)
    {
        try
        {
            _logService.Information("Processing of import directory and file verification begins.");
            EnsureRequiredDirectoriesExist();
            ValidateFilesInRootDirectory();

            // Start processing root folder's files asynchronously without awaiting
            _logService.Information("Starting processing of root folder work items.");
            _ = Task.Run(() => _workItemProcessor.ProcessingFolderFiles(_importDir, processInfo: null, stoppingToken), stoppingToken); // No process info for root folder

            await ProcessProcessFoldersAsync(stoppingToken);
        }
        catch (Exception ex)
        {
            using (LogContext.PushProperty("LogContext", nameof(ProcessImportDirectory)))
                _logService.Error(ex, "Unexpected error occurred while processing import directory.");
        }
    }

    private void EnsureRequiredDirectoriesExist()
    {
        _fileHelper.EnsureDirectoryExists(_importDir, "ImportFolder");
        _fileHelper.EnsureDirectoryExists(_copyErrorLocation, "ErrorFolder");
        _fileHelper.EnsureDirectoryExists(_backupLocation, "CopyLocation");
    }

    private void ValidateFilesInRootDirectory()
    {
        _fileValidation.MoveInvalidFilesToError(_importDir);
    }

    private async Task ProcessProcessFoldersAsync(CancellationToken stoppingToken)
    {
        var validProcesses = await _processService.GetValidProcessesAsync();
        var processFolders = Directory.GetDirectories(_importDir);

        if (!processFolders.Any())
        {
            using (LogContext.PushProperty("LogContext", nameof(ProcessProcessFoldersAsync)))
                _logService.Information("No process folders found in {ImportDir}.", _importDir);
            return;
        }

        using var semaphore = new SemaphoreSlim(_maxDegreeOfParallelism - 1);

        var tasks = processFolders.Select(async processFolder =>
        {
            await semaphore.WaitAsync();

            try
            {
                _logService.Information("Processing folder: {ProcessFolder}", processFolder);
                var processName = Path.GetFileName(processFolder);
                if (!validProcesses.ContainsKey(processName))
                {
                    using (LogContext.PushProperty("LogContext", nameof(ProcessProcessFoldersAsync)))
                    {
                        _logService.Warning("Invalid process folder detected: {ProcessName}. Moving to error location.", processName);
                    }
                    _folderMover.MoveInvalidFolderToError(processFolder, _copyErrorLocation);
                    return;
                }

                ValidateProcessFolderFiles(processFolder);

                var backupFolder = Path.Combine(_backupLocation, processName);
                _fileHelper.EnsureDirectoryExists(backupFolder, "BackupFolder");

                await Task.Run(() => _workItemProcessor.ProcessingFolderFiles(processFolder, validProcesses[processName], stoppingToken), stoppingToken);
            }
            catch (Exception ex)
            {
                using (LogContext.PushProperty("LogContext", nameof(ProcessProcessFoldersAsync)))
                    _logService.Error(ex, $"Error processing folder '{processFolder}'.");
            }
            finally
            {
                semaphore.Release();
            }
        });

        await Task.WhenAll(tasks);
    }

    private void ValidateProcessFolderFiles(string processFolder)
    {
        _fileValidation.MoveInvalidFilesToError(processFolder);
    }
}
