package com.ruoyi.app.omgpay;

import com.ruoyi.app.omgpay.dto.OmgCreatePaymentRequest;
import com.ruoyi.app.omgpay.dto.OmgPaymentErrorResponse;
import com.ruoyi.app.omgpay.dto.OmgNotifyRequest;
import com.ruoyi.app.omgpay.dto.OmgQueryPaymentRequest;
import com.ruoyi.app.omgpay.dto.OmgRefundPaymentRequest;
import com.ruoyi.app.omgpay.dto.OmgRetryPaymentRequest;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.system.utils.Auth;
import com.ruoyi.system.omgpay.service.OmgIpnAuditService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import java.nio.charset.StandardCharsets;

@RestController
@RequestMapping("/pay/omg")
public class OmgPaymentController {
    private static final Logger log = LoggerFactory.getLogger(OmgPaymentController.class);

    private final OmgPaymentTokenUserResolver tokenUserResolver;
    private final OmgPaymentCreateService createService;
    private final OmgPaymentNotifyService notifyService;
    private final OmgIpnAuditService ipnAuditService;
    private final OmgPaymentQueryService queryService;
    private final OmgPaymentRetryService retryService;
    private final OmgPaymentRefundService refundService;

    public OmgPaymentController(OmgPaymentTokenUserResolver tokenUserResolver,
                                OmgPaymentCreateService createService,
                                OmgPaymentNotifyService notifyService,
                                OmgIpnAuditService ipnAuditService,
                                OmgPaymentQueryService queryService,
                                OmgPaymentRetryService retryService,
                                OmgPaymentRefundService refundService) {
        this.tokenUserResolver = tokenUserResolver;
        this.createService = createService;
        this.notifyService = notifyService;
        this.ipnAuditService = ipnAuditService;
        this.queryService = queryService;
        this.retryService = retryService;
        this.refundService = refundService;
    }

    /** OMG server-to-server final payment notification. */
    @Anonymous
    @PostMapping(value = "/notify", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> notify(@OmgNotifyForm OmgNotifyRequest request) {
        String sourceIp = request == null ? "unknown" : request.getSourceIp();
        String rawForm = request == null ? "" : request.getRawForm();
        // Complete callback content is intentionally logged so an operator can copy and replay it.
        log.info("OMG payment notify received ip={}, rawForm={}", sourceIp, rawForm);
        try {
            ipnAuditService.append(sourceIp, rawForm);
        } catch (Exception auditError) {
            // Audit storage is best effort and must never cause loss of an otherwise valid payment fact.
            log.error("OMG payment notify IPN append failed ip={}, rawForm={}",
                    sourceIp, rawForm, auditError);
        }
        try {
            if (notifyService.process(request)) {
                return text("1|OK");
            }
            log.warn("OMG payment notify rejected ip={}, reason={}", sourceIp,
                    request == null ? "REQUEST_MISSING" : request.getInvalidReason());
        } catch (Exception error) {
            log.error("OMG payment notify transaction failed ip={}, rawForm={}", sourceIp, rawForm, error);
        }
        return text("0|ERROR");
    }

    private static ResponseEntity<String> text(String body) {
        return ResponseEntity.ok()
                .contentType(new MediaType("text", "plain", StandardCharsets.UTF_8))
                .body(body);
    }

    @Anonymous
    @Auth
    @PostMapping("/create")
    public AjaxResult create(@RequestHeader(name = "token") String token,
                             @RequestBody(required = false) OmgCreatePaymentRequest request) {
        String orderId = request == null ? null : request.getOrderId();
        String paymentMethodValue = request == null ? null : request.getPaymentMethod();
        String safeOrderId = safeLogOrderId(orderId);
        Long safeUserId = null;
        try {
            safeUserId = tokenUserResolver.requireUserId(token);
            OmgPaymentMethod paymentMethod = OmgPaymentMethod.require(paymentMethodValue);
            log.info("OMG payment create started orderId={}, userId={}", safeOrderId, safeUserId);
            OmgPaymentCreateOutcome outcome = createService.create(safeUserId, orderId, paymentMethod);
            log.info("OMG payment create succeeded orderId={}, userId={}, storeId={}, "
                            + "attemptId={}, amount={}, status=CREATED, merchantTradeNo={}",
                    safeLogOrderId(outcome.orderId()), outcome.userId(), outcome.storeId(), outcome.attemptId(),
                    outcome.amount(), outcome.maskedMerchantTradeNo());
            return AjaxResult.success(outcome.response());
        } catch (OmgPaymentBusinessException error) {
            log.warn("OMG payment create rejected orderId={}, userId={}, storeId={}, code={}",
                    safeOrderId, safeUserId, error.getStoreId(), error.getCode());
            return AjaxResult.error(MessageUtils.message(error.getMessageKey()),
                    new OmgPaymentErrorResponse(error.getCode().name()));
        } catch (Exception error) {
            log.error("OMG payment create failed orderId={}, userId={}", safeOrderId, safeUserId, error);
            return AjaxResult.error(MessageUtils.message("omg.pay.creation.failed"),
                    new OmgPaymentErrorResponse("PAYMENT_CREATION_FAILED"));
        }
    }

    @Anonymous
    @Auth
    @PostMapping("/query")
    public AjaxResult query(@RequestHeader(name = "token") String token,
                            @RequestBody(required = false) OmgQueryPaymentRequest request) {
        String orderId = request == null ? null : request.getOrderId();
        String safeOrderId = safeLogOrderId(orderId);
        Long safeUserId = null;
        try {
            safeUserId = tokenUserResolver.requireUserId(token);
            return AjaxResult.success(queryService.query(safeUserId, orderId));
        } catch (OmgPaymentBusinessException error) {
            log.warn("OMG payment query rejected orderId={}, userId={}, storeId={}, code={}",
                    safeOrderId, safeUserId, error.getStoreId(), error.getCode());
            return AjaxResult.error(MessageUtils.message(error.getMessageKey()),
                    new OmgPaymentErrorResponse(error.getCode().name()));
        } catch (Exception error) {
            log.error("OMG payment query failed orderId={}, userId={}", safeOrderId, safeUserId, error);
            return AjaxResult.error(MessageUtils.message("omg.pay.query.failed"),
                    new OmgPaymentErrorResponse("PAYMENT_QUERY_FAILED"));
        }
    }

    @Anonymous
    @Auth
    @PostMapping("/retry")
    public AjaxResult retry(@RequestHeader(name = "token") String token,
                            @RequestBody(required = false) OmgRetryPaymentRequest request) {
        String orderId = request == null ? null : request.getOrderId();
        String paymentMethodValue = request == null ? null : request.getPaymentMethod();
        String safeOrderId = safeLogOrderId(orderId);
        Long safeUserId = null;
        try {
            safeUserId = tokenUserResolver.requireUserId(token);
            OmgPaymentMethod paymentMethod = OmgPaymentMethod.require(paymentMethodValue);
            log.info("OMG payment retry started orderId={}, userId={}", safeOrderId, safeUserId);
            OmgPaymentCreateOutcome outcome = retryService.retry(safeUserId, orderId, paymentMethod);
            log.info("OMG payment retry succeeded orderId={}, userId={}, storeId={}, "
                            + "attemptId={}, amount={}, status=CREATED, merchantTradeNo={}",
                    safeLogOrderId(outcome.orderId()), outcome.userId(), outcome.storeId(), outcome.attemptId(),
                    outcome.amount(), outcome.maskedMerchantTradeNo());
            return AjaxResult.success(outcome.response());
        } catch (OmgPaymentBusinessException error) {
            log.warn("OMG payment retry rejected orderId={}, userId={}, storeId={}, code={}",
                    safeOrderId, safeUserId, error.getStoreId(), error.getCode());
            return AjaxResult.error(MessageUtils.message(error.getMessageKey()),
                    new OmgPaymentErrorResponse(error.getCode().name()));
        } catch (Exception error) {
            log.error("OMG payment retry failed orderId={}, userId={}", safeOrderId, safeUserId, error);
            return AjaxResult.error(MessageUtils.message("omg.pay.retry.failed"),
                    new OmgPaymentErrorResponse("PAYMENT_RETRY_FAILED"));
        }
    }

    @Anonymous
    @Auth
    @PostMapping("/refund")
    public AjaxResult refund(@RequestHeader(name = "token") String token,
                             @RequestBody(required = false) OmgRefundPaymentRequest request) {
        String orderId = request == null ? null : request.getOrderId();
        String safeOrderId = safeLogOrderId(orderId);
        Long safeUserId = null;
        try {
            safeUserId = tokenUserResolver.requireUserId(token);
            refundService.refund(safeUserId, orderId);
            return AjaxResult.success();
        } catch (OmgPaymentBusinessException error) {
            log.warn("OMG payment refund rejected orderId={}, userId={}, code={}",
                    safeOrderId, safeUserId, error.getCode());
            return AjaxResult.error(MessageUtils.message(error.getMessageKey()),
                    new OmgPaymentErrorResponse(error.getCode().name()));
        } catch (Exception error) {
            log.error("OMG payment refund failed orderId={}, userId={}", safeOrderId, safeUserId, error);
            return AjaxResult.error(MessageUtils.message("omg.pay.refund.failed"),
                    new OmgPaymentErrorResponse("PAYMENT_REFUND_FAILED"));
        }
    }

    static String safeLogOrderId(String orderId) {
        if (orderId == null) {
            return "<empty>";
        }
        StringBuilder safe = new StringBuilder();
        for (int index = 0; index < orderId.length() && safe.length() < 64; index++) {
            char value = orderId.charAt(index);
            if ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')
                    || (value >= '0' && value <= '9') || value == '-' || value == '_') {
                safe.append(value);
            }
        }
        return safe.isEmpty() ? "<empty>" : safe.toString();
    }
}
