using System.Security.Cryptography;
using System.Text.Json;
using Senparc.Weixin.WxOpen.AdvancedAPIs.WxApp;
using Senparc.Weixin.WxOpen.Containers;
using Tiku.Application.Growth;
using Tiku.Application.Storage;
using Tiku.Application.Tenancy;
using Tiku.Domain.Tenancy;

namespace Tiku.Infrastructure.Growth;

public sealed class ReferralQrcodeGenerator(
    IObjectStorageService objectStorageService,
    ITenantExternalProviderConfigService providerConfigService) : IReferralQrcodeGenerator
{
    private static readonly string[] WechatMiniappProviderAliases =
    [
        "wechat-miniapp",
        "wechat_miniapp",
        "wechat-mini",
        "wechatMiniapp"
    ];

    public async Task<ReferralQrcodeGenerateResult> GenerateAsync(
        ReferralQrcodeGenerateRequest request,
        CancellationToken cancellationToken = default)
    {
        var provider = NormalizeProvider(request.Provider);
        return provider == "wechat-miniapp"
            ? await GenerateWechatMiniappAsync(request with { Provider = provider }, cancellationToken)
            : throw new ReferralException("Referral qrcode provider is not supported.", "referral_qrcode_provider_not_supported");
    }

    private async Task<ReferralQrcodeGenerateResult> GenerateWechatMiniappAsync(
        ReferralQrcodeGenerateRequest request,
        CancellationToken cancellationToken)
    {
        var options = await LoadWechatMiniappOptionsAsync(request.TenantId, cancellationToken);
        var imageBytes = await GenerateWechatMiniappImageAsync(options, request, cancellationToken);
        var checksum = Convert.ToHexString(SHA256.HashData(imageBytes)).ToLowerInvariant();
        var objectKey = BuildObjectKey(request.TenantId, request.UserId, request.RefCode, request.Page, request.Scene);
        await using var content = new MemoryStream(imageBytes, writable: false);
        var storage = await objectStorageService.WriteObjectAsync(
            new ObjectStorageWriteRequest(
                request.TenantId,
                objectStorageService.ConfiguredDefaultProvider(),
                objectStorageService.ConfiguredDefaultBucket(),
                objectKey,
                "image/png",
                content,
                imageBytes.LongLength,
                checksum,
                new Dictionary<string, string>
                {
                    ["kind"] = "referral-qrcode",
                    ["provider"] = request.Provider,
                    ["ref-code"] = request.RefCode
                }),
            cancellationToken);

        if (storage.PublicUrl is null)
        {
            throw new ReferralException(
                "Referral qrcode storage public base URL is not configured.",
                "referral_qrcode_public_url_not_configured");
        }

        return new ReferralQrcodeGenerateResult(
            storage.PublicUrl.ToString(),
            request.Provider,
            JsonSerializer.SerializeToElement(new
            {
                generatedBy = "senparc-weixin-wxopen",
                storageProvider = storage.Provider,
                storageBucket = storage.Bucket,
                storageObjectKey = storage.ObjectKey,
                checksumSha256 = storage.ChecksumSha256,
                sizeBytes = storage.SizeBytes
            }));
    }

    private async Task<WechatMiniappOptions> LoadWechatMiniappOptionsAsync(
        Guid tenantId,
        CancellationToken cancellationToken)
    {
        TenantExternalProviderAccount? provider = null;
        foreach (var alias in WechatMiniappProviderAliases)
        {
            try
            {
                provider = await providerConfigService.GetActiveProviderAsync(
                    tenantId,
                    TenantExternalProviderCapability.Identity,
                    alias,
                    cancellationToken);
                break;
            }
            catch (TenantExternalProviderException)
            {
            }
        }

        if (provider is null)
        {
            throw new ReferralException(
                "Wechat miniapp auth provider is not configured.",
                "referral_qrcode_provider_not_configured");
        }

        var appId = GetJsonString(provider.ConfigPublic, "appId", "clientId");
        var appSecret = GetJsonString(provider.SecretPayload, "appSecret", "clientSecret", "secret");
        if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(appSecret))
        {
            throw new ReferralException(
                "Wechat miniapp auth provider configuration is incomplete.",
                "referral_qrcode_provider_not_configured");
        }

        return new WechatMiniappOptions(appId, appSecret);
    }

    private static async Task<byte[]> GenerateWechatMiniappImageAsync(
        WechatMiniappOptions options,
        ReferralQrcodeGenerateRequest request,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        try
        {
            var accessToken = await AccessTokenContainer.TryGetAccessTokenAsync(
                options.AppId,
                options.AppSecret,
                false);
            await using var imageStream = new MemoryStream();
            var result = await WxAppApi.GetWxaCodeUnlimitAsync(
                accessToken,
                imageStream,
                request.Scene,
                request.Page,
                true,
                "release",
                430,
                false,
                null!,
                false,
                20);
            cancellationToken.ThrowIfCancellationRequested();

            if (result.errcode != 0)
            {
                throw new ReferralException(
                    $"Wechat miniapp qrcode generation failed: {result.errcode}.",
                    "referral_qrcode_wechat_failed");
            }

            var imageBytes = imageStream.ToArray();
            if (imageBytes.Length == 0)
            {
                throw new ReferralException(
                    "Wechat miniapp qrcode response was empty.",
                    "referral_qrcode_wechat_empty");
            }

            return imageBytes;
        }
        catch (ReferralException)
        {
            throw;
        }
        catch (Exception exception)
        {
            throw new ReferralException(
                exception is HttpRequestException
                    ? "Wechat miniapp qrcode generation failed with network error."
                    : "Wechat miniapp qrcode generation failed.",
                "referral_qrcode_wechat_failed");
        }
    }

    private static string BuildObjectKey(
        Guid tenantId,
        Guid userId,
        string refCode,
        string page,
        string scene)
    {
        var fingerprint = Convert.ToHexString(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(new
        {
            page,
            scene
        }))).ToLowerInvariant()[..16];
        return $"{tenantId:N}/referral/qrcodes/{userId:N}/{NormalizePathToken(refCode)}-{fingerprint}.png";
    }

    private static string NormalizeProvider(string? provider)
    {
        var normalized = string.IsNullOrWhiteSpace(provider) ? "wechat-miniapp" : provider.Trim();
        return WechatMiniappProviderAliases.Contains(normalized, StringComparer.OrdinalIgnoreCase)
            ? "wechat-miniapp"
            : normalized;
    }

    private static string NormalizePathToken(string value)
    {
        var chars = value
            .Trim()
            .Select(character => char.IsAsciiLetterOrDigit(character) ? character : '-')
            .ToArray();
        var normalized = new string(chars).Trim('-');
        return string.IsNullOrWhiteSpace(normalized) ? "referral" : normalized.ToLowerInvariant();
    }

    private static string? GetJsonString(JsonElement element, params string[] names)
    {
        if (element.ValueKind != JsonValueKind.Object)
        {
            return null;
        }

        foreach (var name in names)
        {
            if (element.TryGetProperty(name, out var property) &&
                property.ValueKind == JsonValueKind.String &&
                !string.IsNullOrWhiteSpace(property.GetString()))
            {
                return property.GetString()!.Trim();
            }
        }

        return null;
    }

    private sealed record WechatMiniappOptions(string AppId, string AppSecret);
}
