package org.dromara.pay.core.impl;

import cn.hutool.core.util.StrUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.app.AppService;
import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
import com.wechat.pay.java.service.payments.app.model.PrepayResponse;
import com.wechat.pay.java.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.Status;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.pay.core.PayStrategy;
import org.dromara.pay.core.PayStrategyFactory;
import org.dromara.pay.domain.PayChannel;
import org.dromara.pay.domain.PayOrder;
import org.dromara.pay.domain.PayRefund;
import org.dromara.pay.domain.vo.PayNotifyResult;
import org.dromara.pay.domain.vo.PayOrderSubmitRespVO;
import org.dromara.pay.enums.PayChannelEnum;
import org.dromara.pay.enums.PayOrderStatusEnum;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.PrivateKey;
import java.util.Map;

import static com.wechat.pay.java.core.http.Constant.*;

/**
 * 微信支付策略
 *
 * @author pengles
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class WechatPayStrategy implements PayStrategy {

    private final PayStrategyFactory payStrategyFactory;

    @PostConstruct
    public void init() {
        payStrategyFactory.register(PayChannelEnum.WX_PUB.getCode(), this);
        payStrategyFactory.register(PayChannelEnum.WX_LITE.getCode(), this);
        payStrategyFactory.register(PayChannelEnum.WX_APP.getCode(), this);
        payStrategyFactory.register(PayChannelEnum.WX_NATIVE.getCode(), this);
        payStrategyFactory.register(PayChannelEnum.WX_H5.getCode(), this);
    }

    @Override
    public PayOrderSubmitRespVO unifiedOrder(PayOrder order, PayChannel channel) {
        Config config = getConfig(channel.getConfig());
        String code = channel.getCode();
        PayOrderSubmitRespVO resp = new PayOrderSubmitRespVO();
        resp.setPayOrderNo(order.getNo());
        resp.setStatus(0); // WAITING

        Map<String, Object> channelConfig = JsonUtils.parseMap(channel.getConfig());
        String appId = (String) channelConfig.get("appId");
        String mchId = (String) channelConfig.get("mchId");
        String privateKeyStr = (String) channelConfig.get("privateKey");
        String mchSerialNo = (String) channelConfig.get("mchSerialNo");

        try {
            if (PayChannelEnum.WX_NATIVE.getCode().equals(code)) {
                NativePayService service = new NativePayService.Builder().config(config).build();
                com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
                request.setAppid(appId);
                request.setMchid(mchId);
                request.setDescription(order.getSubject());
                request.setOutTradeNo(order.getNo());
                request.setNotifyUrl(order.getNotifyUrl());
                com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
                amount.setTotal(order.getPrice());
                request.setAmount(amount);
                
                com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = service.prepay(request);
                resp.setPayData(response.getCodeUrl());
                
            } else if (PayChannelEnum.WX_APP.getCode().equals(code)) {
                AppService service = new AppService.Builder().config(config).build();
                PrepayRequest request = new PrepayRequest();
                request.setAppid(appId);
                request.setMchid(mchId);
                request.setDescription(order.getSubject());
                request.setOutTradeNo(order.getNo());
                request.setNotifyUrl(order.getNotifyUrl());
                com.wechat.pay.java.service.payments.app.model.Amount amount = new com.wechat.pay.java.service.payments.app.model.Amount();
                amount.setTotal(order.getPrice());
                request.setAmount(amount);

                PrepayResponse response = service.prepay(request);
                
                // 二次签名
                PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(privateKeyStr);
                Map<String, String> signMap = WechatPayUtil.signApp(appId, mchId, response.getPrepayId(), privateKey, mchSerialNo);
                resp.setPayData(JsonUtils.toJsonString(signMap));
                
            } else if (PayChannelEnum.WX_PUB.getCode().equals(code) || PayChannelEnum.WX_LITE.getCode().equals(code)) {
                JsapiService service = new JsapiService.Builder().config(config).build();
                com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest request = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
                request.setAppid(appId);
                request.setMchid(mchId);
                request.setDescription(order.getSubject());
                request.setOutTradeNo(order.getNo());
                request.setNotifyUrl(order.getNotifyUrl());
                com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
                amount.setTotal(order.getPrice());
                request.setAmount(amount);
                
                Payer payer = new Payer();
                payer.setOpenid(order.getChannelUserId()); // 必须传入 openid
                request.setPayer(payer);

                com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse response = service.prepay(request);
                
                // 二次签名
                PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(privateKeyStr);
                Map<String, String> signMap = WechatPayUtil.signJsapi(appId, response.getPrepayId(), privateKey, mchSerialNo);
                resp.setPayData(JsonUtils.toJsonString(signMap));
                
            } else if (PayChannelEnum.WX_H5.getCode().equals(code)) {
                H5Service service = new H5Service.Builder().config(config).build();
                com.wechat.pay.java.service.payments.h5.model.PrepayRequest request = new com.wechat.pay.java.service.payments.h5.model.PrepayRequest();
                request.setAppid(appId);
                request.setMchid(mchId);
                request.setDescription(order.getSubject());
                request.setOutTradeNo(order.getNo());
                request.setNotifyUrl(order.getNotifyUrl());
                com.wechat.pay.java.service.payments.h5.model.Amount amount = new com.wechat.pay.java.service.payments.h5.model.Amount();
                amount.setTotal(order.getPrice());
                request.setAmount(amount);
                
                com.wechat.pay.java.service.payments.h5.model.PrepayResponse response = service.prepay(request);
                resp.setPayData(response.getH5Url());
            }
        } catch (Exception e) {
            log.error("微信下单失败", e);
            throw new ServiceException("微信下单失败: " + e.getMessage());
        }
        return resp;
    }

    @Override
    public Boolean refund(PayRefund refund, PayChannel channel) {
        Config config = getConfig(channel.getConfig());
        RefundService service = new RefundService.Builder().config(config).build();
        
        CreateRequest request = new CreateRequest();
        request.setOutTradeNo(refund.getOrderNo());
        request.setOutRefundNo(refund.getNo());
        request.setReason(refund.getReason());
        request.setNotifyUrl(refund.getNotifyUrl());
        
        AmountReq amount = new AmountReq();
        amount.setRefund(Long.valueOf(refund.getRefundPrice()));
        amount.setTotal(Long.valueOf(refund.getPayPrice()));
        amount.setCurrency("CNY");
        request.setAmount(amount);

        try {
            Refund response = service.create(request);
            if (response.getStatus() == Status.SUCCESS || response.getStatus() == Status.PROCESSING) {
                refund.setChannelRefundNo(response.getRefundId());
                return true;
            } else {
                refund.setErrorMsg(response.getStatus().name());
                return false;
            }
        } catch (Exception e) {
            log.error("微信退款失败", e);
            throw new ServiceException("微信退款失败: " + e.getMessage());
        }
    }

    @Override
    public Integer queryOrder(PayOrder order, PayChannel channel) {
        Config config = getConfig(channel.getConfig());
        JsapiService service = new JsapiService.Builder().config(config).build(); // Query is generic across services usually, using JsapiService for access
        
        com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest request = new com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest();
        request.setMchid(getConfigMap(channel.getConfig()).get("mchId").toString());
        request.setOutTradeNo(order.getNo());

        try {
            Transaction transaction = service.queryOrderByOutTradeNo(request);
            if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
                return PayOrderStatusEnum.SUCCESS.getStatus();
            } else if (transaction.getTradeState() == Transaction.TradeStateEnum.CLOSED || transaction.getTradeState() == Transaction.TradeStateEnum.REVOKED) {
                return PayOrderStatusEnum.CLOSED.getStatus();
            } else if (transaction.getTradeState() == Transaction.TradeStateEnum.REFUND) {
                return PayOrderStatusEnum.REFUND.getStatus();
            }
        } catch (Exception e) {
            log.error("微信查单失败", e);
        }
        return PayOrderStatusEnum.WAITING.getStatus();
    }

    @Override
    public Boolean closeOrder(PayOrder order, PayChannel channel) {
        Config config = getConfig(channel.getConfig());
        JsapiService service = new JsapiService.Builder().config(config).build();
        CloseOrderRequest request = new CloseOrderRequest();
        request.setMchid(getConfigMap(channel.getConfig()).get("mchId").toString());
        request.setOutTradeNo(order.getNo());
        try {
            service.closeOrder(request);
            return true;
        } catch (Exception e) {
            log.error("微信关单失败", e);
            return false;
        }
    }

    @Override
    public Integer queryRefund(PayRefund refund, PayChannel channel) {
        Config config = getConfig(channel.getConfig());
        RefundService service = new RefundService.Builder().config(config).build();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(refund.getNo());
        try {
            Refund response = service.queryByOutRefundNo(request);
            if (response.getStatus() == Status.SUCCESS) {
                return 10; // SUCCESS
            }
        } catch (Exception e) {
            log.error("微信退款查询失败", e);
        }
        return 0;
    }

    @Override
    public PayNotifyResult processNotify(HttpServletRequest request, PayChannel channel) {
        PayNotifyResult result = new PayNotifyResult();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
            StringBuilder stringBuffer = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
            String requestBody = stringBuffer.toString();

            String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
            String nonce = request.getHeader(WECHAT_PAY_NONCE);
            String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .body(requestBody)
                    .build();

            // Config config = getConfig(channel.getConfig());
            // NotificationParser parser = new NotificationParser((NotificationConfig) config); // RSAAutoCertificateConfig implements NotificationConfig
            
            // Re-create config here or cast
            NotificationConfig config = (NotificationConfig) getConfig(channel.getConfig());
            NotificationParser parser = new NotificationParser(config);
            
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            if (transaction != null && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
                result.setSuccess(true);
                result.setOrderNo(transaction.getOutTradeNo());
                result.setTradeNo(transaction.getTransactionId());
                // 微信支付V3回调成功需要返回 HTTP 200 或 204，无特定body内容，但建议返回空JSON或success
                // Spring MVC controller usually returns body.
                // We should return a structure that controller can map to ResponseEntity
                result.setResponseBody("{\"code\": \"SUCCESS\", \"message\": \"SUCCESS\"}");
            } else {
                result.setSuccess(false);
                result.setResponseBody("{\"code\": \"FAIL\", \"message\": \"FAIL\"}");
            }
        } catch (Exception e) {
            log.error("微信回调处理异常", e);
            result.setSuccess(false);
            result.setResponseBody("{\"code\": \"FAIL\", \"message\": \"EXCEPTION\"}");
        }
        return result;
    }

    private Config getConfig(String configJson) {
        Map<String, Object> configMap = JsonUtils.parseMap(configJson);
        return new RSAAutoCertificateConfig.Builder()
                .merchantId((String) configMap.get("mchId"))
                .privateKey((String) configMap.get("privateKey"))
                .merchantSerialNumber((String) configMap.get("mchSerialNo"))
                .apiV3Key((String) configMap.get("apiV3Key"))
                .build();
    }
    
    private Map<String, Object> getConfigMap(String configJson) {
        return JsonUtils.parseMap(configJson);
    }
}
