using System.Text.Json;
using Senparc.Weixin.MP.AdvancedAPIs;
using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns;
using Tiku.Application.Auth;

namespace Tiku.Infrastructure.Auth;

public sealed class WechatOAuthClient : IWechatOAuthClient
{
    public async Task<WechatIdentity> ExchangeWebCodeAsync(
        WechatProviderOptions options,
        string code,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        try
        {
            var token = await OAuthApi.GetAccessTokenAsync(
                options.AppId,
                options.AppSecret,
                code);
            cancellationToken.ThrowIfCancellationRequested();
            EnsureSuccess(token.ErrorCodeValue, token.errmsg);

            if (string.IsNullOrWhiteSpace(token.access_token))
                throw new InvalidCredentialsException("wechat_access_token_missing");

            if (string.IsNullOrWhiteSpace(token.openid)) throw new InvalidCredentialsException("wechat_openid_missing");

            var user = await OAuthApi.GetUserInfoAsync(
                token.access_token,
                token.openid);
            cancellationToken.ThrowIfCancellationRequested();

            return new WechatIdentity(
                token.openid.Trim(),
                FirstNonBlank(user.unionid, token.unionid),
                NullIfBlank(user.nickname),
                NullIfBlank(user.headimgurl),
                null,
                SerializeRaw(new
                {
                    accessToken = token,
                    user
                }));
        }
        catch (InvalidCredentialsException)
        {
            throw;
        }
        catch (Exception exception)
        {
            throw new InvalidCredentialsException(MapWechatException(exception));
        }
    }

    public async Task<WechatIdentity> ExchangeMiniAppCodeAsync(
        WechatProviderOptions options,
        string code,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        try
        {
            var result = await SnsApi.JsCode2JsonAsync(
                options.AppId,
                options.AppSecret,
                code);
            cancellationToken.ThrowIfCancellationRequested();
            EnsureSuccess(result.ErrorCodeValue, result.errmsg);

            if (string.IsNullOrWhiteSpace(result.openid))
                throw new InvalidCredentialsException("wechat_openid_missing");

            if (string.IsNullOrWhiteSpace(result.session_key))
                throw new InvalidCredentialsException("wechat_session_key_missing");

            return new WechatIdentity(
                result.openid.Trim(),
                NullIfBlank(result.unionid),
                null,
                null,
                result.session_key.Trim(),
                SerializeRaw(result));
        }
        catch (InvalidCredentialsException)
        {
            throw;
        }
        catch (Exception exception)
        {
            throw new InvalidCredentialsException(MapWechatException(exception));
        }
    }

    private static void EnsureSuccess(int errorCode, string? errorMessage)
    {
        if (errorCode == 0) return;

        throw new InvalidCredentialsException(
            string.IsNullOrWhiteSpace(errorMessage)
                ? "wechat_code_exchange_failed"
                : $"wechat_code_exchange_failed:{errorCode}");
    }

    private static string SerializeRaw<T>(T value)
    {
        return JsonSerializer.Serialize(value);
    }

    private static string? FirstNonBlank(params string?[] values)
    {
        return values.Select(NullIfBlank).FirstOrDefault(value => value is not null);
    }

    private static string? NullIfBlank(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    private static string MapWechatException(Exception exception)
    {
        return exception is HttpRequestException
            ? "wechat_http_error"
            : "wechat_code_exchange_failed";
    }
}