using CashLineERP.Application.Documents;
using CashLineERP.SharedKernel.Documents;

namespace CashLineERP.Infrastructure.Documents;

public sealed class FileSystemManagedComplianceDocumentUploadService : IManagedComplianceDocumentUploadService
{
    private const int MaxFileSizeBytes = 10 * 1024 * 1024;

    private static readonly IReadOnlyDictionary<string, string> AllowedExtensions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        [".pdf"] = "application/pdf",
        [".png"] = "image/png",
        [".jpg"] = "image/jpeg",
        [".jpeg"] = "image/jpeg"
    };

    private readonly string _rootPath;

    public FileSystemManagedComplianceDocumentUploadService(string contentRootPath)
    {
        _rootPath = Path.Combine(contentRootPath, "App_Data", "managed-compliance-documents");
        Directory.CreateDirectory(_rootPath);
    }

    public async Task<ManagedComplianceDocumentUploadResult> UploadAsync(
        string scope,
        string documentType,
        string fileName,
        string? contentType,
        Stream content,
        long? fileSizeBytes,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(scope))
        {
            throw new InvalidOperationException("Upload scope is required.");
        }

        if (string.IsNullOrWhiteSpace(documentType))
        {
            throw new InvalidOperationException("Document type is required.");
        }

        var sanitizedFileName = Path.GetFileName(fileName?.Trim() ?? string.Empty);
        if (string.IsNullOrWhiteSpace(sanitizedFileName))
        {
            throw new InvalidOperationException("Uploaded file name is required.");
        }

        var extension = Path.GetExtension(sanitizedFileName);
        if (string.IsNullOrWhiteSpace(extension) || !AllowedExtensions.TryGetValue(extension, out var canonicalContentType))
        {
            throw new InvalidOperationException("Only PDF, PNG, JPG, and JPEG onboarding documents are supported.");
        }

        if (fileSizeBytes is > MaxFileSizeBytes)
        {
            throw new InvalidOperationException("Onboarding compliance documents must be 10 MB or smaller.");
        }

        if (!string.IsNullOrWhiteSpace(contentType)
            && !string.Equals(contentType.Trim(), canonicalContentType, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(contentType.Trim(), "application/octet-stream", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Uploaded onboarding document content type does not match the supported file format.");
        }

        var safeScope = string.Concat(scope.Trim().ToLowerInvariant().Select(character =>
            char.IsLetterOrDigit(character) || character is '-' or '_' ? character : '-'));
        var dayPartition = DateTime.UtcNow.ToString("yyyy/MM/dd");
        var storedFileName = $"{DateTime.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}{extension.ToLowerInvariant()}";
        var relativeSegments = new[] { "party-compliance", safeScope }
            .Concat(dayPartition.Split('/'))
            .Concat([storedFileName])
            .ToArray();
        var absoluteDirectory = Path.Combine(_rootPath, Path.Combine(relativeSegments[..^1]));
        Directory.CreateDirectory(absoluteDirectory);

        var absolutePath = Path.Combine(absoluteDirectory, storedFileName);
        var bytesWritten = 0;

        try
        {
            await using var output = File.Create(absolutePath);
            var buffer = new byte[81920];

            while (true)
            {
                var read = await content.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken);
                if (read == 0)
                {
                    break;
                }

                bytesWritten += read;
                if (bytesWritten > MaxFileSizeBytes)
                {
                    throw new InvalidOperationException("Onboarding compliance documents must be 10 MB or smaller.");
                }

                await output.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
            }
        }
        catch
        {
            if (File.Exists(absolutePath))
            {
                File.Delete(absolutePath);
            }

            throw;
        }

        var storageReference = $"managed://{string.Join('/', relativeSegments)}";

        return new ManagedComplianceDocumentUploadResult(
            documentType.Trim().ToUpperInvariant(),
            sanitizedFileName,
            storageReference,
            DocumentStorageProviderCode.ManagedStorage,
            canonicalContentType,
            bytesWritten);
    }
}
