using FileAccessAgent.Application.Abstractions;
using FileAccessAgent.Application.Models;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;
using FileAccessAgent.Infrastructure.Configuration;

namespace FileAccessAgent.Infrastructure.Services;

public sealed class LocalFileStorage : IFileStorage
{
    private static readonly FileExtensionContentTypeProvider ContentTypes = new();
    private readonly string _storageRoot;

    public LocalFileStorage(IOptions<InstanceOptions> options)
    {
        var configuredRoot = options.Value.StorageRoot;
        _storageRoot = Path.GetFullPath(string.IsNullOrWhiteSpace(configuredRoot) ? "./data/storage" : configuredRoot);
        Directory.CreateDirectory(_storageRoot);
    }

    public async Task<FileObjectResult> UploadAsync(
        Guid tenantId,
        string objectKey,
        Stream content,
        long? contentLength,
        string? contentType,
        CancellationToken cancellationToken)
    {
        var normalizedKey = ObjectKeyHelper.Normalize(objectKey);
        var destinationPath = BuildPath(tenantId, normalizedKey);
        Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);

        await using var destination = new FileStream(
            destinationPath,
            FileMode.Create,
            FileAccess.Write,
            FileShare.None,
            bufferSize: 1024 * 64,
            useAsync: true);

        await content.CopyToAsync(destination, cancellationToken);
        await destination.FlushAsync(cancellationToken);

        var file = new FileInfo(destinationPath);
        var resolvedType = ResolveContentType(normalizedKey, contentType);

        return new FileObjectResult(
            tenantId,
            normalizedKey,
            file.Length,
            resolvedType,
            file.LastWriteTimeUtc,
            ETag: CreateETag(file));
    }

    public Task<FileObjectResult?> GetMetadataAsync(Guid tenantId, string objectKey, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        var normalizedKey = ObjectKeyHelper.Normalize(objectKey);
        var path = BuildPath(tenantId, normalizedKey);
        if (!File.Exists(path))
        {
            return Task.FromResult<FileObjectResult?>(null);
        }

        var file = new FileInfo(path);
        var result = new FileObjectResult(
            tenantId,
            normalizedKey,
            file.Length,
            ResolveContentType(normalizedKey, null),
            file.LastWriteTimeUtc,
            ETag: CreateETag(file));

        return Task.FromResult<FileObjectResult?>(result);
    }

    public Task<FileDownloadResult?> OpenReadAsync(Guid tenantId, string objectKey, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        var normalizedKey = ObjectKeyHelper.Normalize(objectKey);
        var path = BuildPath(tenantId, normalizedKey);
        if (!File.Exists(path))
        {
            return Task.FromResult<FileDownloadResult?>(null);
        }

        var stream = new FileStream(
            path,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read,
            bufferSize: 1024 * 64,
            useAsync: true);

        var file = new FileInfo(path);
        var result = new FileDownloadResult(
            stream,
            ResolveContentType(normalizedKey, null),
            file.Length,
            file.LastWriteTimeUtc,
            CreateETag(file));

        return Task.FromResult<FileDownloadResult?>(result);
    }

    public Task<bool> DeleteAsync(Guid tenantId, string objectKey, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        var normalizedKey = ObjectKeyHelper.Normalize(objectKey);
        var path = BuildPath(tenantId, normalizedKey);
        if (!File.Exists(path))
        {
            return Task.FromResult(false);
        }

        File.Delete(path);
        return Task.FromResult(true);
    }

    private string BuildPath(Guid tenantId, string objectKey)
    {
        var tenantRoot = Path.Combine(_storageRoot, tenantId.ToString("D"));
        var relativePath = objectKey.Replace('/', Path.DirectorySeparatorChar);
        var fullPath = Path.GetFullPath(Path.Combine(tenantRoot, relativePath));
        if (!fullPath.StartsWith(tenantRoot, StringComparison.Ordinal))
        {
            throw new InvalidOperationException("Invalid object key.");
        }

        return fullPath;
    }

    private static string ResolveContentType(string objectKey, string? requestedContentType)
    {
        if (!string.IsNullOrWhiteSpace(requestedContentType))
        {
            return requestedContentType;
        }

        return ContentTypes.TryGetContentType(objectKey, out var contentType)
            ? contentType
            : "application/octet-stream";
    }

    private static string CreateETag(FileInfo file)
    {
        return $"\"{file.Length:x}-{file.LastWriteTimeUtc.Ticks:x}\"";
    }
}
