package com.ruoyi.app.pay;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.system.domain.PaymentGatewayLog;
import com.ruoyi.system.service.IPaymentGatewayLogService;
import com.ruoyi.app.utils.linepay.LinePayResponse;
import org.springframework.stereotype.Service;

import java.util.UUID;

/** Appends LINE gateway audit rows without making audit availability part of money facts. */
@Service
public class LinePayGatewayAuditService {
    private final IPaymentGatewayLogService gatewayLogService;

    public LinePayGatewayAuditService(IPaymentGatewayLogService gatewayLogService) {
        this.gatewayLogService = gatewayLogService;
    }

    public <T> T execute(String action, String source, Long paymentId, Long refundId,
                         Long credentialId, Long storeId, String ddId,
                         String gatewayOrderId, String transactionId,
                         GatewayCall<T> call) throws Exception {
        long started = System.currentTimeMillis();
        String correlationId = UUID.randomUUID().toString();
        append(correlationId, action, source, paymentId, refundId, credentialId,
                storeId, ddId, gatewayOrderId, transactionId, 0, 0L, null, "REQUEST");
        try {
            T result = call.call();
            append(correlationId, action, source, paymentId, refundId, credentialId,
                    storeId, ddId, gatewayOrderId, transactionId, 1,
                    System.currentTimeMillis() - started,
                    result instanceof LinePayResponse ? (LinePayResponse) result : null, "RESPONSE");
            return result;
        } catch (Exception exception) {
            append(correlationId, action, source, paymentId, refundId, credentialId,
                    storeId, ddId, gatewayOrderId, transactionId, 0,
                    System.currentTimeMillis() - started, null, "RESPONSE");
            throw exception;
        }
    }

    /**
     * Offline Pay audit boundary. The method intentionally accepts no request body or DTO, so the
     * one-time My Code cannot cross into persistence or exception formatting.
     */
    public <T> T executeOffline(String source, Long paymentId, Long credentialId,
                                Long storeId, String ddId, String gatewayOrderId,
                                GatewayCall<T> call) throws Exception {
        long started = System.currentTimeMillis();
        String correlationId = UUID.randomUUID().toString();
        append(correlationId, "REQUEST", source, paymentId, null, credentialId,
                storeId, ddId, gatewayOrderId, null, 0, 0L, null, "REQUEST");
        try {
            T result = call.call();
            LinePayResponse response = result instanceof LinePayResponse
                    ? offlineResponseMetadata((LinePayResponse) result) : null;
            append(correlationId, "REQUEST", source, paymentId, null, credentialId,
                    storeId, ddId, gatewayOrderId, null, 1,
                    System.currentTimeMillis() - started, response, "RESPONSE");
            return result;
        } catch (Exception exception) {
            append(correlationId, "REQUEST", source, paymentId, null, credentialId,
                    storeId, ddId, gatewayOrderId, null, 0,
                    System.currentTimeMillis() - started, null, "RESPONSE");
            throw exception;
        }
    }

    public void event(String action, String source, Long paymentId, Long credentialId,
                      Long storeId, String ddId, String gatewayOrderId, String transactionId) {
        append(UUID.randomUUID().toString(), action, source, paymentId, null, credentialId,
                storeId, ddId, gatewayOrderId, transactionId, 1, 0L, null, "EVENT");
    }

    private void append(String correlationId, String action, String source,
                        Long paymentId, Long refundId, Long credentialId, Long storeId,
                        String ddId, String gatewayOrderId, String transactionId,
                        int success, long durationMs, LinePayResponse response, String direction) {
        PaymentGatewayLog log = new PaymentGatewayLog();
        log.setCorrelationId(correlationId);
        log.setAction(action);
        log.setDirection(direction);
        log.setSource(source);
        log.setPaymentId(paymentId);
        log.setRefundId(refundId);
        log.setCredentialId(credentialId);
        log.setStoreId(storeId);
        log.setDdId(ddId);
        log.setGatewayOrderId(gatewayOrderId);
        log.setTransactionId(response != null && response.transactionId() != null
                ? response.transactionId() : transactionId);
        log.setHttpStatus(response == null ? null : response.httpStatus());
        log.setReturnCode(response == null ? null : response.returnCode());
        log.setReturnMessage(response == null ? null : response.returnMessage());
        log.setPayload(response == null ? null : safePayload(response.rawBody()));
        log.setSuccess(response == null ? success : response.isSuccess() ? 1 : 0);
        log.setDurationMs(durationMs);
        try {
            gatewayLogService.append(log);
        } catch (Exception ignored) {
            // Audit failure must not alter an already known gateway or payment outcome.
        }
    }

    private static String safePayload(String rawBody) {
        if (rawBody == null || rawBody.isBlank()) {
            return rawBody;
        }
        try {
            JSONObject root = JSON.parseObject(rawBody);
            JSONObject info = root.getJSONObject("info");
            if (info != null) {
                info.remove("paymentAccessToken");
                JSONObject paymentUrl = info.getJSONObject("paymentUrl");
                if (paymentUrl != null) {
                    paymentUrl.remove("app");
                    paymentUrl.remove("web");
                }
            }
            return root.toJSONString();
        } catch (RuntimeException ignored) {
            return null;
        }
    }

    private static LinePayResponse offlineResponseMetadata(LinePayResponse response) {
        return new LinePayResponse(response.httpStatus(), response.returnCode(), null,
                response.transactionId(), null, null);
    }

    @FunctionalInterface
    public interface GatewayCall<T> {
        T call() throws Exception;
    }
}
