using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using TakeoutSaaS.Application.App.Members.MessageReach.Options;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;

namespace TakeoutSaaS.Application.App.Members.MessageReach.Services;

/// <summary>
/// 微信小程序订阅消息发送器。
/// </summary>
public sealed class MemberMessageWeChatSender(
    HttpClient httpClient,
    IDistributedCache cache,
    IOptionsMonitor<MemberMessagingOptions> optionsMonitor)
    : IMemberMessageWeChatSender
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true
    };

    /// <inheritdoc />
    public async Task SendAsync(
        string openId,
        string title,
        string content,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(openId))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "openId 不能为空");
        }

        var options = optionsMonitor.CurrentValue.WeChatMini;
        var accessToken = await ResolveAccessTokenAsync(options, cancellationToken);

        var requestBody = new Dictionary<string, object?>
        {
            ["touser"] = openId.Trim(),
            ["template_id"] = options.SubscribeTemplateId,
            ["page"] = options.PagePath,
            ["data"] = new Dictionary<string, object?>
            {
                [options.TitleDataKey] = new { value = Truncate(title, 20) },
                [options.ContentDataKey] = new { value = Truncate(content, 20) }
            }
        };

        var response = await httpClient.PostAsJsonAsync(
            $"cgi-bin/message/subscribe/send?access_token={Uri.EscapeDataString(accessToken)}",
            requestBody,
            cancellationToken);
        response.EnsureSuccessStatusCode();

        var payload = await response.Content.ReadFromJsonAsync<WeChatErrorPayload>(JsonOptions, cancellationToken);
        if (payload is null)
        {
            throw new BusinessException(ErrorCodes.InternalServerError, "微信发送失败：响应为空");
        }

        if (payload.ErrorCode != 0)
        {
            throw new BusinessException(
                ErrorCodes.InternalServerError,
                $"微信发送失败：{payload.ErrorCode} {payload.ErrorMessage}");
        }
    }

    private async Task<string> ResolveAccessTokenAsync(
        MemberMessagingWeChatMiniOptions options,
        CancellationToken cancellationToken)
    {
        var cacheKey = $"member-message:wechat:access-token:{options.AppId}";
        var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
        if (!string.IsNullOrWhiteSpace(cached))
        {
            return cached;
        }

        var response = await httpClient.GetAsync(
            $"cgi-bin/token?grant_type=client_credential&appid={Uri.EscapeDataString(options.AppId)}&secret={Uri.EscapeDataString(options.AppSecret)}",
            cancellationToken);
        response.EnsureSuccessStatusCode();

        var payload = await response.Content.ReadFromJsonAsync<WeChatTokenPayload>(JsonOptions, cancellationToken);
        if (payload is null)
        {
            throw new BusinessException(ErrorCodes.InternalServerError, "微信 access_token 获取失败：响应为空");
        }

        if (payload.ErrorCode != 0)
        {
            throw new BusinessException(
                ErrorCodes.InternalServerError,
                $"微信 access_token 获取失败：{payload.ErrorCode} {payload.ErrorMessage}");
        }

        if (string.IsNullOrWhiteSpace(payload.AccessToken))
        {
            throw new BusinessException(ErrorCodes.InternalServerError, "微信 access_token 获取失败：token 为空");
        }

        var ttlSeconds = payload.ExpiresIn > 120 ? payload.ExpiresIn - 120 : payload.ExpiresIn;
        await cache.SetStringAsync(
            cacheKey,
            payload.AccessToken,
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Math.Max(60, ttlSeconds))
            },
            cancellationToken);

        return payload.AccessToken;
    }

    private static string Truncate(string? value, int maxLength)
    {
        var normalized = (value ?? string.Empty).Trim();
        if (normalized.Length <= maxLength)
        {
            return normalized;
        }

        return normalized[..maxLength];
    }

    private sealed class WeChatTokenPayload
    {
        [JsonPropertyName("access_token")]
        public string? AccessToken { get; set; }

        [JsonPropertyName("expires_in")]
        public int ExpiresIn { get; set; }

        [JsonPropertyName("errcode")]
        public int ErrorCode { get; set; }

        [JsonPropertyName("errmsg")]
        public string? ErrorMessage { get; set; }
    }

    private sealed class WeChatErrorPayload
    {
        [JsonPropertyName("errcode")]
        public int ErrorCode { get; set; }

        [JsonPropertyName("errmsg")]
        public string? ErrorMessage { get; set; }
    }
}

