package com.gxwebsoft.common.core.utils;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 加密二维码工具类
 * 使用token作为密钥对二维码数据进行AES加密
 *
 * @author WebSoft
 * @since 2025-08-18
 */
@Component
public class EncryptedQrCodeUtil {

    @Autowired
    private RedisUtil redisUtil;

    private static final String QR_TOKEN_PREFIX = "qr_token:";
    private static final long DEFAULT_EXPIRE_MINUTES = 30; // 默认30分钟过期

    /**
     * 生成加密的二维码数据
     *
     * @param originalData 原始数据
     * @param expireMinutes 过期时间（分钟）
     * @return 包含token和加密数据的Map
     */
    public Map<String, String> generateEncryptedData(String originalData, Long expireMinutes) {
        if (StrUtil.isBlank(originalData)) {
            throw new IllegalArgumentException("原始数据不能为空");
        }

        if (expireMinutes == null || expireMinutes <= 0) {
            expireMinutes = DEFAULT_EXPIRE_MINUTES;
        }

        // 生成随机token作为密钥
        String token = RandomUtil.randomString(32);

        try {
            // 使用token生成AES密钥
            AES aes = createAESFromToken(token);

            // 加密原始数据
            String encryptedData = aes.encryptHex(originalData);

            // 将token和原始数据存储到Redis中，设置过期时间
            String redisKey = QR_TOKEN_PREFIX + token;
            redisUtil.set(redisKey, originalData, expireMinutes, TimeUnit.MINUTES);

            Map<String, String> result = new HashMap<>();
            result.put("token", token);
            result.put("encryptedData", encryptedData);
            result.put("expireMinutes", expireMinutes.toString());

            return result;

        } catch (Exception e) {
            throw new RuntimeException("生成加密数据失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解密二维码数据
     *
     * @param token 密钥token
     * @param encryptedData 加密的数据
     * @return 解密后的原始数据
     */
    public String decryptData(String token, String encryptedData) {
        if (StrUtil.isBlank(token) || StrUtil.isBlank(encryptedData)) {
            throw new IllegalArgumentException("token和加密数据不能为空");
        }

        try {
            // 从Redis验证token是否有效
            String redisKey = QR_TOKEN_PREFIX + token;
            String originalData = redisUtil.get(redisKey);

            if (StrUtil.isBlank(originalData)) {
                throw new RuntimeException("token已过期或无效");
            }

            // 使用token生成AES密钥
            AES aes = createAESFromToken(token);

            // 解密数据
            String decryptedData = aes.decryptStr(encryptedData, CharsetUtil.CHARSET_UTF_8);

            // 验证解密结果与Redis中存储的数据是否一致
            if (!originalData.equals(decryptedData)) {
                throw new RuntimeException("数据验证失败");
            }

            return decryptedData;

        } catch (Exception e) {
            throw new RuntimeException("解密数据失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成加密的二维码图片（自包含模式）
     *
     * @param originalData 原始数据
     * @param width 二维码宽度
     * @param height 二维码高度
     * @param expireMinutes 过期时间（分钟）
     * @param businessType 业务类型（可选，如：order、user、coupon等）
     * @return 包含二维码图片Base64和token的Map
     */
    public Map<String, Object> generateEncryptedQrCode(String originalData, int width, int height, Long expireMinutes, String businessType) {
        try {
            // 生成加密数据
            Map<String, String> encryptedInfo = generateEncryptedData(originalData, expireMinutes);

            // 创建二维码内容（包含token、加密数据和业务类型）
            Map<String, String> qrContent = new HashMap<>();
            qrContent.put("token", encryptedInfo.get("token"));
            qrContent.put("data", encryptedInfo.get("encryptedData"));
            qrContent.put("type", "encrypted");

            // 添加业务类型（如果提供）
            if (StrUtil.isNotBlank(businessType)) {
                qrContent.put("businessType", businessType);
            }

            String qrDataJson = JSONObject.toJSONString(qrContent);

            // 配置二维码
            QrConfig config = new QrConfig(width, height);
            config.setMargin(1);
            config.setForeColor(Color.BLACK);
            config.setBackColor(Color.WHITE);

            // 生成二维码图片
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            QrCodeUtil.generate(qrDataJson, config, "png", outputStream);

            // 转换为Base64
            String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray());

            Map<String, Object> result = new HashMap<>();
            result.put("qrCodeBase64", base64Image);
            result.put("token", encryptedInfo.get("token"));
            result.put("originalData", originalData);
            result.put("expireMinutes", encryptedInfo.get("expireMinutes"));
            result.put("businessType", businessType);

            return result;

        } catch (Exception e) {
            throw new RuntimeException("生成加密二维码失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成加密的二维码图片（自包含模式，无业务类型）
     * 向后兼容的重载方法
     *
     * @param originalData 原始数据
     * @param width 二维码宽度
     * @param height 二维码高度
     * @param expireMinutes 过期时间（分钟）
     * @return 包含二维码图片Base64和token的Map
     */
    public Map<String, Object> generateEncryptedQrCode(String originalData, int width, int height, Long expireMinutes) {
        return generateEncryptedQrCode(originalData, width, height, expireMinutes, null);
    }

    /**
     * 生成业务加密二维码（门店核销模式）
     * 使用统一的业务密钥，门店可以直接解密
     *
     * @param originalData 原始数据
     * @param width 二维码宽度
     * @param height 二维码高度
     * @param businessKey 业务密钥（如门店密钥）
     * @param expireMinutes 过期时间（分钟）
     * @param businessType 业务类型（如：order、coupon、ticket等）
     * @return 包含二维码图片Base64的Map
     */
    public Map<String, Object> generateBusinessEncryptedQrCode(String originalData, int width, int height,
                                                               String businessKey, Long expireMinutes, String businessType) {
        try {
            if (StrUtil.isBlank(businessKey)) {
                throw new IllegalArgumentException("业务密钥不能为空");
            }

            if (expireMinutes == null || expireMinutes <= 0) {
                expireMinutes = DEFAULT_EXPIRE_MINUTES;
            }

            // 生成唯一的二维码ID
            String qrId = RandomUtil.randomString(16);

            // 使用业务密钥加密数据
            AES aes = createAESFromToken(businessKey);
            String encryptedData = aes.encryptHex(originalData);

            // 将二维码信息存储到Redis（用于验证和防重复使用）
            String qrInfoKey = "qr_info:" + qrId;
            Map<String, String> qrInfo = new HashMap<>();
            qrInfo.put("originalData", originalData);
            qrInfo.put("createTime", String.valueOf(System.currentTimeMillis()));
            qrInfo.put("businessKey", businessKey);
            redisUtil.set(qrInfoKey, JSONObject.toJSONString(qrInfo), expireMinutes, TimeUnit.MINUTES);

            // 创建二维码内容
            Map<String, String> qrContent = new HashMap<>();
            qrContent.put("qrId", qrId);
            qrContent.put("data", encryptedData);
            qrContent.put("type", "business_encrypted");
            qrContent.put("expire", String.valueOf(System.currentTimeMillis() + expireMinutes * 60 * 1000));

            // 添加业务类型（如果提供）
            if (StrUtil.isNotBlank(businessType)) {
                qrContent.put("businessType", businessType);
            }

            String qrDataJson = JSONObject.toJSONString(qrContent);

            // 配置二维码
            QrConfig config = new QrConfig(width, height);
            config.setMargin(1);
            config.setForeColor(Color.BLACK);
            config.setBackColor(Color.WHITE);

            // 生成二维码图片
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            QrCodeUtil.generate(qrDataJson, config, "png", outputStream);

            // 转换为Base64
            String base64Image = Base64.getEncoder().encodeToString(outputStream.toByteArray());

            Map<String, Object> result = new HashMap<>();
            result.put("qrCodeBase64", base64Image);
            result.put("qrId", qrId);
            result.put("originalData", originalData);
            result.put("expireMinutes", expireMinutes.toString());
            result.put("businessType", businessType);
            // 注意：出于安全考虑，不返回businessKey

            return result;

        } catch (Exception e) {
            throw new RuntimeException("生成业务加密二维码失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成业务加密二维码（门店核销模式，无业务类型）
     * 向后兼容的重载方法
     *
     * @param originalData 原始数据
     * @param width 二维码宽度
     * @param height 二维码高度
     * @param businessKey 业务密钥（如门店密钥）
     * @param expireMinutes 过期时间（分钟）
     * @return 包含二维码图片Base64的Map
     */
    public Map<String, Object> generateBusinessEncryptedQrCode(String originalData, int width, int height,
                                                               String businessKey, Long expireMinutes) {
        return generateBusinessEncryptedQrCode(originalData, width, height, businessKey, expireMinutes, null);
    }

    /**
     * 验证并解密二维码内容（自包含模式）
     * 二维码包含token和加密数据，扫码方无需额外信息
     *
     * @param qrContent 二维码扫描得到的内容
     * @return 解密后的原始数据
     */
    public String verifyAndDecryptQrCode(String qrContent) {
        QrCodeDecryptResult result = verifyAndDecryptQrCodeWithResult(qrContent);
        return result.getOriginalData();
    }

    /**
     * 验证并解密二维码内容（自包含模式，返回完整结果）
     * 包含业务类型等详细信息
     *
     * @param qrContent 二维码扫描得到的内容
     * @return 包含解密数据和业务类型的完整结果
     */
    public QrCodeDecryptResult verifyAndDecryptQrCodeWithResult(String qrContent) {
        try {
            // 解析二维码内容
            @SuppressWarnings("unchecked")
            Map<String, String> contentMap = JSONObject.parseObject(qrContent, Map.class);

            String type = contentMap.get("type");

            // 严格验证二维码类型，防止前端伪造
            if (!isValidQrCodeType(type, "encrypted")) {
                throw new RuntimeException("无效的二维码类型或二维码已被篡改");
            }

            String token = contentMap.get("token");
            String encryptedData = contentMap.get("data");

            // 验证必要字段
            if (StrUtil.isBlank(token) || StrUtil.isBlank(encryptedData)) {
                throw new RuntimeException("二维码数据不完整");
            }

            String businessType = contentMap.get("businessType"); // 获取业务类型

            // 解密数据（自包含模式：token就在二维码中）
            String originalData = decryptData(token, encryptedData);

            // 返回包含业务类型的完整结果
            return QrCodeDecryptResult.createEncryptedResult(originalData, businessType);

        } catch (Exception e) {
            throw new RuntimeException("验证和解密二维码失败: " + e.getMessage(), e);
        }
    }

    /**
     * 验证并解密二维码内容（业务模式）
     * 适用于门店核销场景：门店有统一的解密密钥
     *
     * @param qrContent 二维码扫描得到的内容
     * @param businessKey 业务密钥（如门店密钥）
     * @return 解密后的原始数据
     */
    public String verifyAndDecryptQrCodeWithBusinessKey(String qrContent, String businessKey) {
        try {
            // 解析二维码内容
            @SuppressWarnings("unchecked")
            Map<String, String> contentMap = JSONObject.parseObject(qrContent, Map.class);

            String type = contentMap.get("type");
            if (!"business_encrypted".equals(type)) {
                throw new RuntimeException("不是业务加密类型的二维码");
            }

            String encryptedData = contentMap.get("data");
            String qrId = contentMap.get("qrId"); // 二维码唯一ID

            // 验证二维码是否已被使用（防止重复核销）
            String usedKey = "qr_used:" + qrId;
            if (StrUtil.isNotBlank(redisUtil.get(usedKey))) {
                throw new RuntimeException("二维码已被使用");
            }

            // 使用业务密钥解密
            AES aes = createAESFromToken(businessKey);
            String decryptedData = aes.decryptStr(encryptedData, CharsetUtil.CHARSET_UTF_8);

            // 标记二维码为已使用（24小时过期，防止重复使用）
            redisUtil.set(usedKey, "used", 24L, TimeUnit.HOURS);

            return decryptedData;

        } catch (Exception e) {
            throw new RuntimeException("业务验证和解密二维码失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除token（使二维码失效）
     *
     * @param token 要删除的token
     */
    public void invalidateToken(String token) {
        if (StrUtil.isNotBlank(token)) {
            String redisKey = QR_TOKEN_PREFIX + token;
            redisUtil.delete(redisKey);
        }
    }

    /**
     * 检查token是否有效
     *
     * @param token 要检查的token
     * @return true表示有效，false表示无效或过期
     */
    public boolean isTokenValid(String token) {
        if (StrUtil.isBlank(token)) {
            return false;
        }

        String redisKey = QR_TOKEN_PREFIX + token;
        String data = redisUtil.get(redisKey);
        return StrUtil.isNotBlank(data);
    }

    /**
     * 验证二维码类型是否有效
     *
     * @param actualType 实际的类型
     * @param expectedType 期望的类型
     * @return true表示有效，false表示无效
     */
    private boolean isValidQrCodeType(String actualType, String expectedType) {
        return expectedType.equals(actualType);
    }

    /**
     * 根据token创建AES加密器
     *
     * @param token 密钥token
     * @return AES加密器
     */
    private AES createAESFromToken(String token) {
        // 使用token生成固定长度的密钥
        String keyString = SecureUtil.md5(token);
        // 取前16字节作为AES密钥
        byte[] keyBytes = keyString.substring(0, 16).getBytes(CharsetUtil.CHARSET_UTF_8);
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, SymmetricAlgorithm.AES.getValue());
        return SecureUtil.aes(secretKey.getEncoded());
    }
}
