using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
using System.Text;
using TakeoutSaaS.Application.Storage.Abstractions;
using TakeoutSaaS.Application.Storage.Contracts;
using TakeoutSaaS.Application.Storage.Enums;
using TakeoutSaaS.Application.Storage.Extensions;
using TakeoutSaaS.Module.Storage.Abstractions;
using TakeoutSaaS.Module.Storage.Models;
using TakeoutSaaS.Module.Storage.Options;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Storage.Services;

/// <summary>
/// 文件存储应用服务，实现上传与直传凭证生成。
/// </summary>
public sealed class FileStorageService(
    IStorageProviderResolver providerResolver,
    IOptionsMonitor<StorageOptions> optionsMonitor,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor currentUserAccessor,
    ILogger<FileStorageService> logger) : IFileStorageService
{
    /// <inheritdoc />
    public async Task<FileUploadResponse> UploadAsync(UploadFileRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 校验请求
        if (request is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "上传请求不能为空");
        }

        // 2. 读取安全配置并校验来源/大小/类型
        var options = optionsMonitor.CurrentValue;
        var security = options.Security;
        ValidateOrigin(request.RequestOrigin, security);
        ValidateFileSize(request.ContentLength, security);

        var extension = NormalizeExtension(request.FileName);
        ValidateExtension(request.FileType, extension, security);
        var contentType = NormalizeContentType(request.ContentType, extension);
        ResetStream(request.Content);

        // 3. 生成对象键与元数据
        var objectKey = BuildObjectKey(request.FileType, extension);
        var metadata = BuildMetadata(request.FileType);
        var expires = TimeSpan.FromMinutes(Math.Max(1, security.DefaultUrlExpirationMinutes));
        var provider = providerResolver.Resolve();

        // 4. 上传到对象存储
        var uploadResult = await provider.UploadAsync(
            new StorageUploadRequest(objectKey, request.Content, contentType, request.ContentLength, false, expires, metadata),
            cancellationToken).ConfigureAwait(false);

        // 5. 构造公开访问链接并返回
        var finalUrl = provider.BuildPublicUrl(uploadResult.ObjectKey);
        logger.LogInformation("文件上传成功：{ObjectKey} ({Size} bytes)", objectKey, request.ContentLength);

        return new FileUploadResponse
        {
            Url = finalUrl,
            FileName = Path.GetFileName(uploadResult.ObjectKey),
            FileSize = uploadResult.FileSize
        };
    }

    /// <inheritdoc />
    public async Task<DirectUploadResponse> CreateDirectUploadAsync(DirectUploadRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 校验请求
        if (request is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "直传请求不能为空");
        }

        // 2. 校验来源/大小/类型
        var options = optionsMonitor.CurrentValue;
        var security = options.Security;
        ValidateOrigin(request.RequestOrigin, security);
        ValidateFileSize(request.ContentLength, security);

        var extension = NormalizeExtension(request.FileName);
        ValidateExtension(request.FileType, extension, security);
        var contentType = NormalizeContentType(request.ContentType, extension);

        // 3. 构建直传参数
        var objectKey = BuildObjectKey(request.FileType, extension);
        var provider = providerResolver.Resolve();
        var expires = TimeSpan.FromMinutes(Math.Max(1, security.DefaultUrlExpirationMinutes));

        // 4. 向存储获取直传凭证
        var directResult = await provider.CreateDirectUploadAsync(
            new StorageDirectUploadRequest(objectKey, contentType, request.ContentLength, expires),
            cancellationToken).ConfigureAwait(false);

        // 5. 构造直传结果并追加防盗链
        var finalDownloadUrl = directResult.SignedDownloadUrl != null
            ? AppendAntiLeechToken(directResult.SignedDownloadUrl, objectKey, expires, security)
            : null;

        return new DirectUploadResponse
        {
            UploadUrl = directResult.UploadUrl,
            FormFields = directResult.FormFields,
            ExpiresAt = directResult.ExpiresAt,
            ObjectKey = directResult.ObjectKey,
            DownloadUrl = finalDownloadUrl
        };
    }

    /// <summary>
    /// 校验文件大小。
    /// </summary>
    private static void ValidateFileSize(long size, StorageSecurityOptions security)
    {
        if (size <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "文件内容为空");
        }

        if (size > security.MaxFileSizeBytes)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"文件过大，最大允许 {security.MaxFileSizeBytes / 1024 / 1024}MB");
        }
    }

    /// <summary>
    /// 校验文件后缀是否符合配置。
    /// </summary>
    private static void ValidateExtension(UploadFileType type, string extension, StorageSecurityOptions security)
    {
        var allowedImages = security.AllowedImageExtensions ?? Array.Empty<string>();
        var allowedFiles = security.AllowedFileExtensions ?? Array.Empty<string>();

        if (type is UploadFileType.DishImage or UploadFileType.MerchantLogo or UploadFileType.UserAvatar or UploadFileType.ReviewImage)
        {
            if (!allowedImages.Contains(extension, StringComparer.OrdinalIgnoreCase))
            {
                throw new BusinessException(ErrorCodes.BadRequest, $"不支持的图片格式：{extension}");
            }
        }
        else if (!allowedFiles.Contains(extension, StringComparer.OrdinalIgnoreCase))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"不支持的文件格式：{extension}");
        }
    }

    /// <summary>
    /// 统一化文件后缀（小写，默认 .bin）。
    /// </summary>
    private static string NormalizeExtension(string fileName)
    {
        var extension = Path.GetExtension(fileName);
        if (string.IsNullOrWhiteSpace(extension))
        {
            return ".bin";
        }

        return extension.ToLowerInvariant();
    }

    /// <summary>
    /// 根据内容类型或后缀推断 Content-Type。
    /// </summary>
    private static string NormalizeContentType(string contentType, string extension)
    {
        if (!string.IsNullOrWhiteSpace(contentType))
        {
            return contentType;
        }

        return extension switch
        {
            ".jpg" or ".jpeg" => "image/jpeg",
            ".png" => "image/png",
            ".gif" => "image/gif",
            ".webp" => "image/webp",
            ".pdf" => "application/pdf",
            _ => "application/octet-stream"
        };
    }

    /// <summary>
    /// 校验请求来源是否在白名单内。
    /// </summary>
    private void ValidateOrigin(string? origin, StorageSecurityOptions security)
    {
        if (!security.EnableRefererValidation || security.AllowedReferers.Length == 0)
        {
            return;
        }

        if (string.IsNullOrWhiteSpace(origin))
        {
            throw new BusinessException(ErrorCodes.Forbidden, "未授权的访问来源");
        }

        var isAllowed = security.AllowedReferers.Any(allowed =>
            !string.IsNullOrWhiteSpace(allowed) &&
            origin.StartsWith(allowed, StringComparison.OrdinalIgnoreCase));

        if (!isAllowed)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "访问来源未在白名单中");
        }
    }

    /// <summary>
    /// 生成对象存储的键路径。
    /// </summary>
    private string BuildObjectKey(UploadFileType type, string extension)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var tenantSegment = tenantId == 0 ? "platform" : tenantId.ToString();
        var folder = type.ToFolderName();
        var now = DateTime.UtcNow;
        var fileName = $"{Guid.NewGuid():N}{extension}";

        return $"{tenantSegment}/{folder}/{now:yyyy/MM/dd}/{fileName}";
    }

    /// <summary>
    /// 组装对象元数据，便于追踪租户与用户。
    /// </summary>
    private IDictionary<string, string> BuildMetadata(UploadFileType type)
    {
        var metadata = new Dictionary<string, string>
        {
            ["x-meta-upload-type"] = type.ToString(),
            ["x-meta-tenant-id"] = tenantProvider.GetCurrentTenantId().ToString()
        };

        if (currentUserAccessor.IsAuthenticated)
        {
            metadata["x-meta-user-id"] = currentUserAccessor.UserId.ToString();
        }

        return metadata;
    }

    /// <summary>
    /// 重置文件流的读取位置。
    /// </summary>
    private static void ResetStream(Stream stream)
    {
        if (stream.CanSeek)
        {
            stream.Position = 0;
        }
    }

    /// <summary>
    /// 为访问链接追加防盗链签名（可配合 CDN Token 验证）。
    /// </summary>
    private static string AppendAntiLeechToken(string url, string objectKey, TimeSpan expires, StorageSecurityOptions security)
    {
        if (string.IsNullOrWhiteSpace(security.AntiLeechTokenSecret))
        {
            return url;
        }

        // 若链接已包含云厂商签名参数，则避免追加自定义参数导致验签失败。
        if (url.Contains("X-Amz-Signature", StringComparison.OrdinalIgnoreCase) ||
            url.Contains("q-sign-algorithm", StringComparison.OrdinalIgnoreCase) ||
            url.Contains("Signature=", StringComparison.OrdinalIgnoreCase))
        {
            return url;
        }

        var expireAt = DateTimeOffset.UtcNow.Add(expires).ToUnixTimeSeconds();
        var payload = $"{objectKey}:{expireAt}:{security.AntiLeechTokenSecret}";
        var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(payload));
        var token = Convert.ToHexString(hashBytes).ToLowerInvariant();
        var separator = url.Contains('?', StringComparison.Ordinal) ? "&" : "?";
        return $"{url}{separator}ts={expireAt}&token={token}";
    }
}
