package com.huimv.employment.service.wechat;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxaCodeUnlimit;
import cn.binarywang.wx.miniapp.executor.QrcodeBytesRequestExecutor;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.service.config.RegistrationQrProperties;
import com.huimv.employment.service.config.WeChatMiniAppProperties;
import me.chanjar.weixin.common.error.WxErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;

import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Qrcode.GET_WXACODE_UNLIMIT_URL;

/**
 * 微信小程序：登录会话与登记小程序码。
 */
@Service
public class WeChatMiniAppService {

    private static final Logger log = LoggerFactory.getLogger(WeChatMiniAppService.class);

    /** 微信 scene 允许字符（不含中文空格等） */
    private static final Pattern SCENE_PATTERN = Pattern.compile("^[0-9A-Za-z!#$&'()*+,/:;=?@\\-._~]{1,32}$");

    private final WeChatMiniAppProperties properties;
    private final RegistrationQrProperties registrationQrProperties;
    private final ObjectProvider<WxMaService> wxMaServiceProvider;

    public WeChatMiniAppService(WeChatMiniAppProperties properties,
                                RegistrationQrProperties registrationQrProperties,
                                ObjectProvider<WxMaService> wxMaServiceProvider) {
        this.properties = properties;
        this.registrationQrProperties = registrationQrProperties;
        this.wxMaServiceProvider = wxMaServiceProvider;
    }

    public WxSession code2Session(String code) {
        if (!StringUtils.hasText(code)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "微信 code 不能为空");
        }
        if (properties.isMockEnabled()) {
            return new WxSession(properties.getMockOpenid(), null, "mock_session_key");
        }
        WxMaService wxMaService = wxMaServiceProvider.getIfAvailable();
        if (wxMaService == null) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, "微信小程序未配置");
        }
        try {
            WxMaJscode2SessionResult result = wxMaService.getUserService().getSessionInfo(code);
            return new WxSession(result.getOpenid(), result.getUnionid(), result.getSessionKey());
        } catch (WxErrorException ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "微信登录失败，请重试");
        }
    }

    /**
     * 生成不限制的小程序码。
     * <p>
     * access_token 由 WxMaService 按
     * {@code GET /cgi-bin/token?grant_type=client_credential&appid&secret} 自动获取；
     * 码体对应 {@code POST /wxa/getwxacodeunlimit}，scene 传订单编号（最长 32）。
     * </p>
     */
    public byte[] createRegistrationWxaCode(String scene) {
        if (!StringUtils.hasText(scene)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "小程序码 scene（订单编号）不能为空");
        }
        String normalizedScene = scene.trim();
        if (!SCENE_PATTERN.matcher(normalizedScene).matches()) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "小程序码 scene 非法（仅支持 1-32 位数字/字母及 !#$&'()*+,/:;=?@-._~）：" + scene);
        }
        if (properties.isMockEnabled()) {
            return createMockQrPng(normalizedScene, registrationQrProperties.getWidth());
        }
        WxMaService wxMaService = wxMaServiceProvider.getIfAvailable();
        if (wxMaService == null) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, "微信小程序未配置");
        }
        String page = registrationQrProperties.getPage();
        if (page != null && page.startsWith("/")) {
            page = page.substring(1);
        }
        boolean checkPath = registrationQrProperties.isCheckPath();
        try {
            return invokeGetWxaCodeUnlimit(wxMaService, normalizedScene, page, checkPath);
        } catch (WxErrorException ex) {
            // 页面未发布到对应 env 时常见 41030 / 85079；自动降级为 check_path=false 再试一次
            if (checkPath && isPagePathError(ex)) {
                log.warn("check_path=true 生成失败，降级 check_path=false 重试 scene={}, page={}, err={}",
                        normalizedScene, page, formatWxError(ex));
                try {
                    return invokeGetWxaCodeUnlimit(wxMaService, normalizedScene, page, false);
                } catch (WxErrorException retryEx) {
                    throw toQrBizException(normalizedScene, page, retryEx);
                }
            }
            throw toQrBizException(normalizedScene, page, ex);
        }
    }

    /**
     * 组装与微信文档一致的请求体，禁止把 line_color 置 null（否则报「请求的参数不正确」）。
     */
    private byte[] invokeGetWxaCodeUnlimit(WxMaService wxMaService,
                                           String scene,
                                           String page,
                                           boolean checkPath) throws WxErrorException {
        WxaCodeUnlimit req = new WxaCodeUnlimit();
        req.setScene(scene);
        req.setPage(page);
        req.setCheckPath(checkPath);
        String env = registrationQrProperties.getEnvVersion();
        req.setEnvVersion(StringUtils.hasText(env) ? env.trim() : "release");
        int width = registrationQrProperties.getWidth();
        req.setWidth(width > 0 ? width : 430);
        // 保持 bean 默认 auto_color=true / line_color={"r":"0","g":"0","b":"0"}，勿 setLineColor(null)
        log.info("调用 getwxacodeunlimit body={}", req.toJson());
        return wxMaService.execute(
                QrcodeBytesRequestExecutor.create(wxMaService.getRequestHttp()),
                GET_WXACODE_UNLIMIT_URL,
                req);
    }

    private static boolean isPagePathError(WxErrorException ex) {
        if (ex.getError() == null) {
            return false;
        }
        int code = ex.getError().getErrorCode();
        // 41030 invalid page；85079 page not found / not published for env
        return code == 41030 || code == 85079 || code == 40165 || code == 40212;
    }

    private BizException toQrBizException(String scene, String page, WxErrorException ex) {
        String detail = formatWxError(ex);
        log.warn("生成登记小程序码失败 scene={}, page={}, env={}, err={}",
                scene, page, registrationQrProperties.getEnvVersion(), detail);
        return new BizException(ErrorCode.INTERNAL_ERROR,
                "生成登记小程序码失败：" + detail
                        + "（请确认 access_token 有效，且 pages/worker/job-offer 已上传到 "
                        + registrationQrProperties.getEnvVersion()
                        + "；未发布时可将 fe.registration.qr.check-path 设为 false）");
    }

    private static String formatWxError(WxErrorException ex) {
        if (ex.getError() == null) {
            return ex.getMessage();
        }
        return "errcode=" + ex.getError().getErrorCode() + ", " + ex.getError().getErrorMsg();
    }

    /** 微信失败时的降级 PNG（不依赖 mock-enabled / 网络）。 */
    public byte[] createRegistrationWxaCodeForFallback(String scene) {
        return createMockQrPng(scene, registrationQrProperties.getWidth());
    }

    private static byte[] createMockQrPng(String scene, int width) {
        int size = width > 0 ? width : 430;
        BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        try {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, size, size);
            g.setColor(Color.BLACK);
            g.drawRect(8, 8, size - 16, size - 16);
            g.setFont(new Font("SansSerif", Font.BOLD, 18));
            g.drawString("MOCK WXA CODE", 24, size / 2 - 10);
            g.setFont(new Font("SansSerif", Font.PLAIN, 14));
            g.drawString(scene, 24, size / 2 + 20);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ImageIO.write(image, "png", out);
            return out.toByteArray();
        } catch (Exception ex) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, "生成 Mock 小程序码失败");
        } finally {
            g.dispose();
        }
    }

    public static class WxSession {
        private final String openid;
        private final String unionid;
        private final String sessionKey;

        public WxSession(String openid, String unionid, String sessionKey) {
            this.openid = openid;
            this.unionid = unionid;
            this.sessionKey = sessionKey;
        }

        public String getOpenid() {
            return openid;
        }

        public String getUnionid() {
            return unionid;
        }

        public String getSessionKey() {
            return sessionKey;
        }
    }
}
