using System.Text.Json;
using AlibabaCloud.OpenApiClient.Models;
using AlibabaCloud.SDK.Dysmsapi20170525.Models;
using Tiku.Application.Auth;
using Tiku.Application.Tenancy;
using Tiku.Domain.Tenancy;
using DysmsClient = AlibabaCloud.SDK.Dysmsapi20170525.Client;

namespace Tiku.Infrastructure.Auth;

internal sealed class AliyunSmsProvider(
    ITenantExternalProviderConfigService providerConfigService) : ISmsProvider
{
    private const string ProviderCode = "aliyun_sms";
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    public async Task<SmsProviderSendResult> SendAsync(
        SmsProviderSendRequest request,
        CancellationToken cancellationToken = default)
    {
        TenantExternalProviderAccount account;
        try
        {
            account = await providerConfigService.GetActiveProviderAsync(
                request.TenantId,
                TenantExternalProviderCapability.Sms,
                null,
                cancellationToken);
        }
        catch (TenantExternalProviderException)
        {
            return new SmsProviderSendResult("noop", "accepted");
        }

        if (string.Equals(account.Provider, "noop", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(account.Provider, "local_dev", StringComparison.OrdinalIgnoreCase))
            return new SmsProviderSendResult(account.Provider, "accepted");

        if (!string.Equals(account.Provider, ProviderCode, StringComparison.OrdinalIgnoreCase))
            throw new SmsProviderException(
                $"SMS provider '{account.Provider}' is not supported.",
                "sms_provider_unsupported");

        var signName = Required(account.ConfigPublic, "signName", "smsSignName");
        var templateCode = ResolveTemplateCode(account.ConfigPublic, request.Purpose);
        var accessKeyId = Required(account.SecretPayload, "accessKeyId");
        var accessKeySecret = Required(account.SecretPayload, "accessKeySecret");
        var endpoint = Optional(account.ConfigPublic, "endpoint") ?? "dysmsapi.aliyuncs.com";
        var regionId = Optional(account.ConfigPublic, "regionId", "region") ?? "cn-hangzhou";

        var client = new DysmsClient(new Config
        {
            AccessKeyId = accessKeyId,
            AccessKeySecret = accessKeySecret,
            Endpoint = endpoint,
            RegionId = regionId
        });

        var sendRequest = new SendSmsRequest
        {
            PhoneNumbers = request.Phone,
            SignName = signName,
            TemplateCode = templateCode,
            TemplateParam = BuildTemplateParam(account.ConfigPublic, request),
            OutId = request.TenantId.ToString("N")
        };

        try
        {
            var response = await client.SendSmsAsync(sendRequest).WaitAsync(cancellationToken);
            var body = response.Body;
            if (body is null)
                throw new SmsProviderException(
                    "Aliyun SMS returned an empty response.",
                    "aliyun_sms_empty_response");

            if (!string.Equals(body.Code, "OK", StringComparison.OrdinalIgnoreCase))
                throw new SmsProviderException(
                    $"Aliyun SMS send failed: {body.Code}.",
                    "aliyun_sms_send_rejected");

            return new SmsProviderSendResult(
                ProviderCode,
                body.Code ?? "OK",
                body.BizId ?? body.RequestId);
        }
        catch (SmsProviderException)
        {
            throw;
        }
        catch (Exception exception) when (exception is not OperationCanceledException)
        {
            throw new SmsProviderException(
                "Aliyun SMS send failed.",
                "aliyun_sms_send_failed",
                exception);
        }
    }

    private static string ResolveTemplateCode(JsonElement config, SmsPurpose purpose)
    {
        if (config.ValueKind == JsonValueKind.Object &&
            config.TryGetProperty("templateCodes", out var templateCodes) &&
            templateCodes.ValueKind == JsonValueKind.Object)
        {
            var purposeKey = purpose.ToString().ToLowerInvariant();
            if (templateCodes.TryGetProperty(purposeKey, out var purposeTemplate) &&
                purposeTemplate.ValueKind == JsonValueKind.String &&
                !string.IsNullOrWhiteSpace(purposeTemplate.GetString()))
                return purposeTemplate.GetString()!;
        }

        return Required(config, "templateCode", "smsTemplateCode");
    }

    private static string BuildTemplateParam(JsonElement config, SmsProviderSendRequest request)
    {
        var codeField = Optional(config, "codeField") ?? "code";
        return JsonSerializer.Serialize(
            new Dictionary<string, string>
            {
                [codeField] = request.Code
            },
            JsonOptions);
    }

    private static string Required(JsonElement element, params string[] keys)
    {
        var value = Optional(element, keys);
        if (!string.IsNullOrWhiteSpace(value)) return value;

        throw new SmsProviderException(
            "Aliyun SMS provider configuration is incomplete.",
            "aliyun_sms_config_incomplete");
    }

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

        foreach (var key in keys)
            if (element.TryGetProperty(key, out var property) &&
                property.ValueKind == JsonValueKind.String &&
                !string.IsNullOrWhiteSpace(property.GetString()))
                return property.GetString();

        return null;
    }
}