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

namespace IWS5.Importer.Worker.FileSystem;

public class FileMover : IFileMover
{
    private readonly ILogger _logService;
    private readonly string _importDirectory;
    private readonly string _copyErrorLocation;
    private readonly IFileHelper _fileHelper;
    private readonly HashSet<string> _allowedDocTypes;
    public FileMover(
        ILogger logService,
        IGeneralImportSettingsProvider settingsProvider,
        IFileHelper fileHelper)
    {
        _logService = logService;
        _fileHelper = fileHelper;
        var config = settingsProvider.Settings;
        _importDirectory = config.ImportDirs;
        _allowedDocTypes = settingsProvider.Settings.AllowedExtensions.ToHashSet() ?? new HashSet<string>();
        _copyErrorLocation = config.CopyErrorLocation;
    }

    public IReadOnlyList<(string filePath, string reason)> GetUnsupportedFilesFor(string folderPath)
    {
        var unsupported = GetUnsupportedFiles(folderPath, SearchOption.TopDirectoryOnly);
        return unsupported.Select(x => (x.filePath, "Unsupported extension or invalid name")).ToList();
    }

    public void MoveInvalidFilesToError(string folderPath, SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        using (LogContext.PushProperty("LogContext", nameof(FileMover)))
        {
            try
            {
                var unsupported = GetUnsupportedFiles(folderPath, searchOption);

                foreach (var (filePath, relativePath) in unsupported)
                {
                    string destinationPath = Path.Combine(_copyErrorLocation, relativePath);
                    string destinationDir = Path.GetDirectoryName(destinationPath)!;

                    _fileHelper.EnsureDirectoryExists(destinationDir, "InvalidFiles");
                    string finalPath = GetUnicFilePath(destinationPath);

                    File.Move(filePath, finalPath);

                    _logService.Warning("Moved unsupported file {Source} to {Destination}", filePath, finalPath);
                }
            }
            catch (Exception ex)
            {
                _logService.Error(ex, "Error while moving invalid files from {FolderPath} to error folder.", folderPath);
            }
        }
    }

    public void MoveFilesToError(List<string> sourceFilePaths)
    {
        if (sourceFilePaths == null || sourceFilePaths.Count == 0)
            return;

        using (LogContext.PushProperty("LogContext", nameof(MoveFilesToError)))
        {
            // Group files by their directory relative to the import root
            var fileGroups = sourceFilePaths.GroupBy(file =>
            {
                var relativeDir = Path.GetDirectoryName(Path.GetRelativePath(_importDirectory, file));
                return relativeDir;
            });

            foreach (var fileGroup in fileGroups)
            {
                string relativeDir = fileGroup.Key ?? string.Empty;
                string destinationDir = Path.Combine(_copyErrorLocation, relativeDir);

                // If this is a workitem-level (or deeper) folder, ensure we append a timestamp once
                var pathSegments = relativeDir.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                if (pathSegments.Length >= 2) // at least process/workitem
                {
                    destinationDir = _fileHelper.GetUniqueDestinationPath(destinationDir);
                }

                _fileHelper.EnsureDirectoryExists(destinationDir, "ErrorFilesBatchMove");

                foreach (var sourceFile in fileGroup)
                {
                    try
                    {
                        string destinationFilePath = Path.Combine(destinationDir, Path.GetFileName(sourceFile));
                        destinationFilePath = GetUnicFilePath(destinationFilePath);

                        File.Move(sourceFile, destinationFilePath);

                        _logService.Warning("Moved unsupported file {Source} to error folder: {Destination}",
                            sourceFile, destinationFilePath);
                    }
                    catch (Exception ex)
                    {
                        _logService.Error(ex, "Failed to move file {SourceFilePath} to error folder.", sourceFile);
                    }
                }
            }
        }
    }

    public void MoveFileToProcessed(string sourceFilePath, string destinationPath)
    {
        using (LogContext.PushProperty("LogContext", nameof(FileMover)))
        {
            try
            {
                if (string.IsNullOrWhiteSpace(sourceFilePath) || string.IsNullOrWhiteSpace(destinationPath))
                {
                    _logService.Warning("Invalid source or destination path: {SourceFilePath}, {DestinationPath}", sourceFilePath, destinationPath);
                    return;
                }

                if (!File.Exists(sourceFilePath))
                {
                    _logService.Warning("Source file does not exist: {SourceFilePath}", sourceFilePath);
                    return;
                }

                string relativePath = Path.GetRelativePath(_importDirectory, sourceFilePath);
                string destinationFilePath = Path.Combine(destinationPath, relativePath);

                string destinationFolder = Path.GetDirectoryName(destinationFilePath)
                    ?? throw new InvalidOperationException($"Could not determine directory for path: {destinationPath}");
                _fileHelper.EnsureDirectoryExists(destinationFolder, "ProcessedFiles");

                destinationFilePath = GetUnicFilePath(destinationFilePath);

                File.Move(sourceFilePath, destinationFilePath);
                _logService.Information("Moved processed file {SourceFilePath} to processed folder: {DestinationFilePath}", sourceFilePath, destinationFilePath);
            }
            catch (Exception ex)
            {
                _logService.Error(ex, "Failed to move processed file {SourceFilePath} to processed folder.", sourceFilePath);
            }
        }
    }

    private string GetUnicFilePath(string destinationFilePath)
    {
        if (!File.Exists(destinationFilePath))
            return destinationFilePath;

        var directory = Path.GetDirectoryName(destinationFilePath)!;
        var filenameWithoutExt = Path.GetFileNameWithoutExtension(destinationFilePath);
        var extension = Path.GetExtension(destinationFilePath);
        var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");

        return Path.Combine(directory, $"{filenameWithoutExt}_{timestamp}{extension}");
    }

    private List<(string filePath, string relativePath)> GetUnsupportedFiles(string folderPath, SearchOption searchOption)
    {
        var result = new List<(string filePath, string relativePath)>();

        if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
        {
            _logService.Warning("Invalid or non-existent folder path: {FolderPath}", folderPath);
            return result;
        }

        var allFiles = Directory.GetFiles(folderPath, "*", searchOption);

        foreach (var file in allFiles)
        {
            string fileName = Path.GetFileName(file);
            string extension = Path.GetExtension(file).TrimStart('.').ToLowerInvariant();

            if (fileName.Equals("done.txt", StringComparison.OrdinalIgnoreCase) ||
                _allowedDocTypes.Contains(extension, StringComparer.OrdinalIgnoreCase))
            {
                continue;
            }

            string relativePath = Path.GetRelativePath(_importDirectory, file);
            result.Add((file, relativePath));
            _logService.Warning("File {FileName} is not supported.", fileName);
        }

        return result;
    }
}
