using Microsoft.Extensions.Hosting;

namespace ERPCore.Infra.Storage;

/// <summary>
/// Disk-backed <see cref="IFileStorageService"/>. Writes under a configured root
/// OUTSIDE wwwroot (<c>FileStorage:RootPath</c>, default <c>App_Data/hr-documents</c>
/// relative to the content root) so files are never reachable via a static-file URL —
/// the only way to read one back is through an authenticated controller action that
/// streams via <see cref="OpenReadAsync"/> (docs/12-BACKEND-HRM.md A.1, §4).
/// </summary>
public sealed class LocalFileStorageService : IFileStorageService
{
    private readonly string _root;

    public LocalFileStorageService(IHostEnvironment env, IConfiguration configuration)
    {
        var configuredRoot = configuration["FileStorage:RootPath"] ?? "App_Data/hr-documents";
        _root = Path.IsPathRooted(configuredRoot) ? configuredRoot : Path.Combine(env.ContentRootPath, configuredRoot);
        Directory.CreateDirectory(_root);
    }

    public async Task<(string StoredFileName, string RelativePath, long SizeBytes)> SaveAsync(
        Stream content, string suggestedFileName, string contentType, CancellationToken ct = default)
    {
        var extension = Path.GetExtension(suggestedFileName);
        var storedFileName = $"{Guid.NewGuid():N}{extension}";

        // Bucket by year/month so a single directory never grows unbounded.
        var subDir = Path.Combine(DateTime.UtcNow.Year.ToString(), DateTime.UtcNow.Month.ToString("00"));
        var absoluteDir = Path.Combine(_root, subDir);
        Directory.CreateDirectory(absoluteDir);

        var relativePath = Path.Combine(subDir, storedFileName).Replace('\\', '/');
        var absolutePath = Path.Combine(_root, relativePath);

        await using (var fileStream = new FileStream(absolutePath, FileMode.CreateNew, FileAccess.Write))
        {
            await content.CopyToAsync(fileStream, ct);
        }

        var sizeBytes = new FileInfo(absolutePath).Length;
        return (storedFileName, relativePath, sizeBytes);
    }

    public Task<Stream> OpenReadAsync(string relativePath, CancellationToken ct = default)
    {
        var absolutePath = ResolveSafe(relativePath);
        Stream stream = new FileStream(absolutePath, FileMode.Open, FileAccess.Read);
        return Task.FromResult(stream);
    }

    public Task DeleteAsync(string relativePath, CancellationToken ct = default)
    {
        var absolutePath = ResolveSafe(relativePath);
        if (File.Exists(absolutePath)) File.Delete(absolutePath);
        return Task.CompletedTask;
    }

    public Task<bool> ExistsAsync(string relativePath, CancellationToken ct = default)
    {
        var absolutePath = ResolveSafe(relativePath);
        return Task.FromResult(File.Exists(absolutePath));
    }

    /// <summary>Resolves a stored relative path and rejects any attempt to escape the storage root.</summary>
    private string ResolveSafe(string relativePath)
    {
        var absolutePath = Path.GetFullPath(Path.Combine(_root, relativePath));
        var rootFull = Path.GetFullPath(_root);
        if (!absolutePath.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
            throw new UnauthorizedAccessException("Resolved path escapes the file storage root.");
        return absolutePath;
    }
}
