using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using TakeoutSaaS.Module.Storage.Abstractions;
using TakeoutSaaS.Module.Storage.Models;

namespace TakeoutSaaS.Module.Storage.Providers;

/// <summary>
/// 基于 AWS S3 SDK 的通用存储提供商基类，可复用到 COS 与 Kodo 等兼容实现。
/// </summary>
public abstract class S3StorageProviderBase : IObjectStorageProvider, IDisposable
{
    private IAmazonS3? _client;
    private bool _disposed;

    /// <inheritdoc />
    public abstract StorageProviderKind Kind { get; }

    /// <summary>
    /// 目标桶名称。
    /// </summary>
    protected abstract string Bucket { get; }

    /// <summary>
    /// S3 服务端点，需包含协议。
    /// </summary>
    protected abstract string ServiceUrl { get; }

    /// <summary>
    /// 访问凭证 ID。
    /// </summary>
    protected abstract string AccessKey { get; }

    /// <summary>
    /// 访问凭证密钥。
    /// </summary>
    protected abstract string SecretKey { get; }

    /// <summary>
    /// 是否使用 HTTPS。
    /// </summary>
    protected abstract bool UseHttps { get; }

    /// <summary>
    /// 是否强制 PathStyle 访问。
    /// </summary>
    protected abstract bool ForcePathStyle { get; }

    /// <summary>
    /// CDN 域名（可选）。
    /// </summary>
    protected abstract string? CdnBaseUrl { get; }

    /// <summary>
    /// 默认签名有效期。
    /// </summary>
    protected abstract TimeSpan SignedUrlExpiry { get; }

    /// <inheritdoc />
    public virtual async Task<StorageUploadResult> UploadAsync(StorageUploadRequest request, CancellationToken cancellationToken = default)
    {
        // 0. 兜底重置流位置，避免上游读取导致内容缺失
        if (request.Content.CanSeek)
        {
            request.Content.Position = 0;
        }

        // 1. 构建上传请求
        var putRequest = new PutObjectRequest
        {
            BucketName = Bucket,
            Key = request.ObjectKey,
            InputStream = request.Content,
            AutoCloseStream = false,
            ContentType = request.ContentType,
            DisableDefaultChecksumValidation = true,
            UseChunkEncoding = false,
            DisablePayloadSigning = true
        };

        // 1.1 显式设置 Content-Length，避免 S3 SDK 对兼容网关使用 aws-chunked 导致对象内容被写入“chunk-signature”头而损坏
        if (request.ContentLength > 0)
        {
            putRequest.Headers.ContentLength = request.ContentLength;
        }

        foreach (var kv in request.Metadata)
        {
            putRequest.Metadata[kv.Key] = kv.Value;
        }

        // 2. 执行上传
        await Client.PutObjectAsync(putRequest, cancellationToken).ConfigureAwait(false);

        // 3. 根据需要生成签名 URL
        var signedUrl = request.GenerateSignedUrl
            ? GenerateSignedUrl(request.ObjectKey, request.SignedUrlExpires)
            : null;

        // 4. 返回上传结果
        return new StorageUploadResult
        {
            ObjectKey = request.ObjectKey,
            Url = signedUrl ?? BuildPublicUrl(request.ObjectKey),
            SignedUrl = signedUrl,
            FileSize = request.ContentLength,
            ContentType = request.ContentType
        };
    }

    /// <inheritdoc />
    public virtual Task<StorageDirectUploadResult> CreateDirectUploadAsync(StorageDirectUploadRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 计算过期时间并生成直传 URL
        var expiresAt = DateTimeOffset.UtcNow.Add(request.Expires);
        var uploadUrl = GenerateSignedUrl(request.ObjectKey, request.Expires, HttpVerb.PUT, request.ContentType);
        var signedDownload = GenerateSignedUrl(request.ObjectKey, request.Expires);

        // 2. 返回直传参数
        var result = new StorageDirectUploadResult
        {
            UploadUrl = uploadUrl,
            FormFields = new Dictionary<string, string>(),
            ExpiresAt = expiresAt,
            ObjectKey = request.ObjectKey,
            SignedDownloadUrl = signedDownload
        };

        return Task.FromResult(result);
    }

    /// <inheritdoc />
    public virtual Task<string> GenerateDownloadUrlAsync(string objectKey, TimeSpan expires, CancellationToken cancellationToken = default)
    {
        // 1. 生成下载签名 URL
        var url = GenerateSignedUrl(objectKey, expires);
        return Task.FromResult(url);
    }

    /// <inheritdoc />
    public virtual string BuildPublicUrl(string objectKey)
    {
        if (!string.IsNullOrWhiteSpace(CdnBaseUrl))
        {
            return $"{CdnBaseUrl!.TrimEnd('/')}/{objectKey}";
        }

        var endpoint = new Uri(ServiceUrl);
        var scheme = UseHttps ? "https" : "http";
        return $"{scheme}://{Bucket}.{endpoint.Host}/{objectKey}";
    }

    /// <summary>
    /// 生成预签名 URL。
    /// </summary>
    /// <param name="objectKey">对象键。</param>
    /// <param name="expires">过期时间。</param>
    /// <param name="verb">HTTP 动作。</param>
    /// <param name="contentType">可选的内容类型约束。</param>
    protected virtual string GenerateSignedUrl(string objectKey, TimeSpan expires, HttpVerb verb = HttpVerb.GET, string? contentType = null)
    {
        var request = new GetPreSignedUrlRequest
        {
            BucketName = Bucket,
            Key = objectKey,
            Verb = verb,
            Expires = DateTime.UtcNow.Add(expires),
            Protocol = UseHttps ? Protocol.HTTPS : Protocol.HTTP
        };

        if (!string.IsNullOrWhiteSpace(contentType))
        {
            request.Headers["Content-Type"] = contentType;
        }

        return Client.GetPreSignedURL(request);
    }

    /// <summary>
    /// 创建 S3 客户端。
    /// </summary>
    protected virtual IAmazonS3 CreateClient()
    {
        // 1. 尽量推断鉴权 Region（腾讯云 COS 的 S3 兼容域名形如：cos.ap-beijing.myqcloud.com）
        var authenticationRegion = ResolveAuthenticationRegion(ServiceUrl);

        // 2. 构建客户端配置
        var config = new AmazonS3Config
        {
            ServiceURL = ServiceUrl,
            ForcePathStyle = ForcePathStyle,
            UseHttp = !UseHttps,
            AuthenticationRegion = authenticationRegion
        };

        // 3. 创建客户端并返回
        var credentials = new BasicAWSCredentials(AccessKey, SecretKey);
        return new AmazonS3Client(credentials, config);
    }

    private static string? ResolveAuthenticationRegion(string serviceUrl)
    {
        // 1. 解析 URL
        if (!Uri.TryCreate(serviceUrl, UriKind.Absolute, out var uri))
        {
            return null;
        }

        // 2. 提取 Host
        var host = uri.Host;
        if (string.IsNullOrWhiteSpace(host))
        {
            return null;
        }

        // 3. 按 COS 域名规则解析 Region
        const string cosPrefix = "cos.";
        const string cosSuffix = ".myqcloud.com";
        if (host.StartsWith(cosPrefix, StringComparison.OrdinalIgnoreCase) &&
            host.EndsWith(cosSuffix, StringComparison.OrdinalIgnoreCase))
        {
            var regionPart = host.Substring(cosPrefix.Length, host.Length - cosPrefix.Length - cosSuffix.Length);
            return string.IsNullOrWhiteSpace(regionPart) ? null : regionPart;
        }
        return null;
    }

    private IAmazonS3 Client => _client ??= CreateClient();

    /// <inheritdoc />
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;
        _client?.Dispose();
        GC.SuppressFinalize(this);
    }
}
