package org.wfc.payment.pay.stripe.service.impl;

import cn.hutool.core.util.StrUtil;
import com.stripe.Stripe;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.Event;
import com.stripe.model.EventDataObjectDeserializer;
import com.stripe.model.PaymentIntent;
import com.stripe.model.StripeObject;
import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook;
import com.stripe.param.checkout.SessionCreateParams;
import com.stripe.param.checkout.SessionRetrieveParams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import org.wfc.common.core.domain.R;
import org.wfc.payment.domain.StripeBean;
import org.wfc.payment.pay.stripe.service.IStripeService;
import org.wfc.user.api.RemoteUUserService;
import org.wfc.user.api.domain.vo.UOrderVo;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

/**
 * @author: cyc
 * @since: 2025-04-24
 */
@Slf4j
@Service
public class StripeServiceImpl implements IStripeService {

    @Resource
    private RemoteUUserService remoteUUserService;

    @Resource
    private StripeBean stripeBean;

    @Override
    public String stripePay(Long orderId) {

        Stripe.apiKey = stripeBean.getSecretKey();
        String YOUR_DOMAIN = stripeBean.getDomain();

        UOrderVo orderVo = getByOrderId(orderId);
        Long totalFee = orderVo.getOrderAmount().multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
        String productName = orderVo.getType() == 0 ? "Package" : "Recharge";

        String currency = "usd";
        if (StrUtil.isNotBlank(orderVo.getCurrency())) {
            currency = orderVo.getCurrency().toLowerCase();
        }
        SessionCreateParams params =
                SessionCreateParams.builder()
                        .setMode(SessionCreateParams.Mode.PAYMENT)
                        .setSuccessUrl(YOUR_DOMAIN + "")
                        .setCancelUrl(YOUR_DOMAIN + "")
                        .setClientReferenceId(orderId.toString())
                        .addLineItem(
                                SessionCreateParams.LineItem.builder()
                                        .setQuantity(1L)
                                        .setPriceData(
                                                SessionCreateParams.LineItem.PriceData.builder()
                                                        .setCurrency(currency)
                                                        .setUnitAmount(totalFee)
                                                        .setProductData(
                                                                SessionCreateParams.LineItem.PriceData.ProductData.builder()
                                                                        .setName(productName)
                                                                        .build())
                                                        .build())
                                        .build())
                        .build();

        Session session = null;
        try {
            session = Session.create(params);
        } catch (StripeException e) {
            log.error("Stripe create session error", e);
            throw new RuntimeException(e);
        }
        return session.getUrl();
    }

    @Override
    public String webhook(HttpServletRequest request, HttpServletResponse response) {
        Stripe.apiKey = stripeBean.getSecretKey();
        // Replace this endpoint secret with your endpoint's unique secret
        // If you are testing with the CLI, find the secret by running 'stripe listen'
        // If you are using an endpoint defined with the API or dashboard, look in your webhook settings
        // at https://dashboard.stripe.com/webhooks
        String endpointSecret = stripeBean.getEndpointSecret();

        String payload = null;
        try {
            InputStream inputStream = request.getInputStream();
            byte[] bytes = IOUtils.toByteArray(inputStream);
            payload = new String(bytes, "UTF-8");
        } catch (IOException e) {
            log.error("Stripe webhook payload error", e);
            response.setStatus(400);
            return "";
        }

//        String payload = request.body();
        Event event = null;

//        try {
//            event = ApiResource.GSON.fromJson(payload, Event.class);
//        } catch (JsonSyntaxException e) {
//            // Invalid payload
//            System.out.println("⚠️  Webhook error while parsing basic request.");
//            response.status(400);
//            return "";
//        }
        String sigHeader = request.getHeader("Stripe-Signature");
        if (endpointSecret != null && sigHeader != null) {
            // Only verify the event if you have an endpoint secret defined.
            // Otherwise use the basic event deserialized with GSON.
            try {
                event = Webhook.constructEvent(
                        payload, sigHeader, endpointSecret
                );
            } catch (SignatureVerificationException e) {
                // Invalid signature
                log.error("Webhook error while validating signature.", e);
                response.setStatus(400);
                return "";
            }
        }
        // Deserialize the nested object inside the event
        EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
        StripeObject stripeObject = null;
        if (dataObjectDeserializer.getObject().isPresent()) {
            stripeObject = dataObjectDeserializer.getObject().get();
        } else {
            // Deserialization failed, probably due to an API version mismatch.
            // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for
            // instructions on how to handle this case, or return an error here.
        }
        // Handle the event
        switch (event.getType()) {
            case "payment_intent.succeeded":
                PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
                System.out.println("Payment for " + paymentIntent.getAmount() + " succeeded.");
                // Then define and call a method to handle the successful payment intent.
                // handlePaymentIntentSucceeded(paymentIntent);
                break;
            case "checkout.session.completed":
//                TreeMap data = JSONObject.parseObject(dataObjectDeserializer.getRawJson(), TreeMap.class);
//                String preOrderId = data.get("client_reference_id").toString();

                Session sessionEvent= (Session) stripeObject;

                fulfillCheckout(sessionEvent);

                // Then define and call a method to handle the successful attachment of a PaymentMethod.
                // handlePaymentMethodAttached(paymentMethod);
                break;
            default:
                log.error("Unhandled event type: {}", event.getType());
                break;
        }
        response.setStatus(200);
        return "";
    }

    public void fulfillCheckout(Session sessionEvent) {
        // Set your secret key. Remember to switch to your live secret key in production.
        // See your keys here: https://dashboard.stripe.com/apikeys
        Stripe.apiKey = stripeBean.getSecretKey();

        String sessionId = sessionEvent.getId();
        log.info("Fulfilling Checkout Session {}", sessionId);

        // Make this function safe to run multiple times,
        // even concurrently, with the same session ID

        // Make sure fulfillment hasn't already been
        // performed for this Checkout Session

        // Retrieve the Checkout Session from the API with line_items expanded
        SessionRetrieveParams params =
                SessionRetrieveParams.builder()
                        .addExpand("line_items")
                        .build();

        Session checkoutSession = null;
        try {
            checkoutSession = Session.retrieve(sessionId, params, null);
        } catch (StripeException e) {
            log.error("Stripe webhook retrieve error", e);
            throw new RuntimeException(e);
        }

        // Check the Checkout Session's payment_status property
        // to determine if fulfillment should be performed
        if (!Objects.equals(checkoutSession.getPaymentStatus(), "unpaid")) {
            // Perform fulfillment of the line items

            // Record/save fulfillment status for this
            // Checkout Session
            remoteUUserService.paySuccess(Long.valueOf(sessionEvent.getClientReferenceId()), "inner");
        }
    }

    private UOrderVo getByOrderId(Long orderId) {
        R<UOrderVo> orderRes = remoteUUserService.getOrderById(orderId);
        UOrderVo orderVo = orderRes.getData();

        return orderVo;
    }
}
