package com.ruoyi.app.omgpay;

import com.ruoyi.app.order.OrderLifecycleService;

import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
import com.ruoyi.system.domain.PosStoreOmg;
import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
import com.ruoyi.system.omgpay.domain.OmgPaymentOrderSnapshot;
import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
import com.ruoyi.system.service.IPosStoreOmgService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.*;

@Service
public class OmgPaymentCreateService {
    private static final Logger log = LoggerFactory.getLogger(OmgPaymentCreateService.class);
    private static final int MAX_TRADE_NUMBER_ATTEMPTS = 3;

    private final IOmgPaymentAttemptService attempts;
    private final IPosStoreOmgService credentials;
    private final OmgMerchantTradeNoGenerator generator;
    private final OmgPaymentFormFactory formFactory;

    public OmgPaymentCreateService(IOmgPaymentAttemptService attempts,
                                   IPosStoreOmgService credentials,
                                   OmgMerchantTradeNoGenerator generator,
                                   OmgPaymentFormFactory formFactory) {
        this.attempts = attempts;
        this.credentials = credentials;
        this.generator = generator;
        this.formFactory = formFactory;
    }

    @Transactional(rollbackFor = Exception.class)
    public OmgPaymentCreateOutcome create(Long userId, String orderId, OmgPaymentMethod paymentMethod) {
        requirePaymentMethod(paymentMethod);
        if (userId == null) {
            throw business(AUTH_REQUIRED);
        }
        String normalizedOrderId = normalizeOrderId(orderId);
        OmgPaymentOrderSnapshot order = attempts.selectOrderForUpdate(normalizedOrderId);
        validateOrder(userId, normalizedOrderId, order);
        if (attempts.selectActiveCreatedByDdIdForUpdate(normalizedOrderId) != null) {
            throw business(PAYMENT_ATTEMPT_EXISTS, order.getStoreId());
        }
        PosStoreOmg credential = credentials.getEnabledCredential(order.getStoreId());
        validateCredential(credential, order.getStoreId());
        log.info("OMG payment validation passed orderId={}, userId={}, storeId={}",
                safeLogOrderId(order.getDdId()), userId, order.getStoreId());
        return createWithBoundedTradeNumberRetries(order, userId, credential, paymentMethod);
    }

    /**
     * Replaces the exact attempt previously verified as unpaid by the gateway.
     * The order lock and MerchantTradeNo comparison prevent concurrent retries from both creating a new attempt.
     */
    @Transactional(rollbackFor = Exception.class)
    public OmgPaymentCreateOutcome replaceActiveForRetry(Long userId, String orderId,
                                                         String expectedMerchantTradeNo,
                                                         OmgPaymentMethod paymentMethod) {
        requirePaymentMethod(paymentMethod);
        if (userId == null) {
            throw business(AUTH_REQUIRED);
        }
        String normalizedOrderId = normalizeOrderId(orderId);
        OmgPaymentOrderSnapshot order = attempts.selectOrderForUpdate(normalizedOrderId);
        validateOrder(userId, normalizedOrderId, order);
        OmgPaymentAttempt active = attempts.selectActiveCreatedByDdIdForUpdate(normalizedOrderId);
        if (active == null || active.getId() == null || expectedMerchantTradeNo == null
                || !expectedMerchantTradeNo.equals(active.getMerchantTradeNo())) {
            throw business(PAYMENT_RETRY_NOT_AVAILABLE, order.getStoreId());
        }
        PosStoreOmg credential = credentials.getEnabledCredential(order.getStoreId());
        validateCredential(credential, order.getStoreId());
        if (attempts.supersedeCreated(active.getId()) != 1) {
            throw business(PAYMENT_RETRY_NOT_AVAILABLE, order.getStoreId());
        }
        log.info("OMG payment retry replacing attempt orderId={}, userId={}, storeId={}, oldMerchantTradeNo={}",
                safeLogOrderId(order.getDdId()), userId, order.getStoreId(),
                maskMerchantTradeNo(active.getMerchantTradeNo()));
        return createWithBoundedTradeNumberRetries(order, userId, credential, paymentMethod);
    }

    private OmgPaymentCreateOutcome createWithBoundedTradeNumberRetries(
            OmgPaymentOrderSnapshot order, Long userId, PosStoreOmg credential,
            OmgPaymentMethod paymentMethod) {
        for (int number = 1; number <= MAX_TRADE_NUMBER_ATTEMPTS; number++) {
            String merchantTradeNo = generator.generate();
            OmgPaymentForm form;
            try {
                form = formFactory.create(order.getDdId(), order.getAmount(), credential.getMerchantId(),
                        credential.getHashKey(), credential.getHashIv(), merchantTradeNo, paymentMethod);
            } catch (IllegalArgumentException error) {
                throw business(PAYMENT_CONFIGURATION_INVALID, order.getStoreId());
            }
            try {
                OmgPaymentAttempt attempt = attempts.createCreated(order.getDdId(), merchantTradeNo,
                        order.getStoreId(), credential.getMerchantId(), order.getAmount(),
                        credential.getHashKey(), credential.getHashIv());
                OmgCreatePaymentResponse response = new OmgCreatePaymentResponse(form.gatewayUrl(), form.fields());
                return new OmgPaymentCreateOutcome(response, attempt.getId(), order.getDdId(), userId,
                        order.getStoreId(), order.getAmount(), maskMerchantTradeNo(merchantTradeNo));
            } catch (DuplicateKeyException error) {
                if (attempts.selectActiveCreatedByDdIdForUpdate(order.getDdId()) != null) {
                    throw business(PAYMENT_ATTEMPT_EXISTS, order.getStoreId());
                }
                boolean tradeNumberCollision = attempts.selectByMerchantTradeNoForUpdate(merchantTradeNo) != null;
                if (!tradeNumberCollision || number == MAX_TRADE_NUMBER_ATTEMPTS) {
                    throw business(PAYMENT_CREATION_FAILED, order.getStoreId());
                }
            }
        }
        throw business(PAYMENT_CREATION_FAILED, order.getStoreId());
    }

    private static String normalizeOrderId(String orderId) {
        if (orderId == null || orderId.isBlank()) {
            throw business(ORDER_REQUIRED);
        }
        String normalized = orderId.trim();
        if (normalized.length() > 64) {
            throw business(ORDER_REQUIRED);
        }
        return normalized;
    }

    private static void requirePaymentMethod(OmgPaymentMethod paymentMethod) {
        if (paymentMethod == null) {
            throw business(PAYMENT_METHOD_INVALID);
        }
    }

    private static void validateOrder(Long userId, String orderId, OmgPaymentOrderSnapshot order) {
        if (order == null || !userId.equals(order.getUserId())) {
            throw business(ORDER_NOT_AVAILABLE);
        }
        Long storeId = order.getStoreId();
        if (order.getParentDdId() == null || !orderId.equals(order.getParentDdId())) {
            throw business(MULTI_STORE_ORDER_NOT_SUPPORTED, storeId);
        }
        if (storeId == null) {
            throw business(ORDER_NOT_AVAILABLE);
        }
        if (order.getState() == null || order.getState() < 0 || order.getState() > 2) {
            throw business(ORDER_STATE_NOT_PAYABLE, storeId);
        }
        if (order.getPayStatus() == null || order.getPayStatus() != 0) {
            throw business(ORDER_ALREADY_PAID, storeId);
        }
        if (!OrderLifecycleService.isCardWalletPayType(order.getPayType())) {
            throw business(PAYMENT_TYPE_INVALID, storeId);
        }
        if (order.getAmount() == null || order.getAmount() <= 0) {
            throw business(ORDER_AMOUNT_INVALID, storeId);
        }
    }

    private static void validateCredential(PosStoreOmg credential, Long storeId) {
        if (credential == null || credential.getMerchantId() == null
                || !credential.getMerchantId().matches("[A-Za-z0-9]{1,10}")
                || credential.getHashKey() == null || credential.getHashKey().isBlank()
                || credential.getHashIv() == null || credential.getHashIv().isBlank()) {
            throw business(STORE_CREDENTIAL_UNAVAILABLE, storeId);
        }
    }

    static String maskMerchantTradeNo(String merchantTradeNo) {
        if (merchantTradeNo == null || merchantTradeNo.length() < 10) {
            return "***";
        }
        return merchantTradeNo.substring(0, 5) + "***"
                + merchantTradeNo.substring(merchantTradeNo.length() - 4);
    }

    private 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();
    }

    private static OmgPaymentBusinessException business(OmgPaymentErrorCode code) {
        return new OmgPaymentBusinessException(code);
    }

    private static OmgPaymentBusinessException business(OmgPaymentErrorCode code, Long storeId) {
        return new OmgPaymentBusinessException(code, storeId);
    }
}
