package com.ruoyi.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.PosStoreLinePay;
import com.ruoyi.system.domain.dto.StoreLinePayCredentialDto;
import com.ruoyi.system.domain.vo.PosStoreLinePayVo;
import com.ruoyi.system.mapper.PosStoreLinePayMapper;
import com.ruoyi.system.service.IPosStoreLinePayService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
public class PosStoreLinePayServiceImpl implements IPosStoreLinePayService {

    private static final String VERIFIED = "VERIFIED";
    private final PosStoreLinePayMapper credentialMapper;

    public PosStoreLinePayServiceImpl(PosStoreLinePayMapper credentialMapper) {
        this.credentialMapper = credentialMapper;
    }

    @Override
    public PosStoreLinePay getCurrent(Long storeId) {
        return storeId == null ? null : credentialMapper.selectCurrentByStoreId(storeId);
    }

    @Override
    public PosStoreLinePay getEnabledCurrent(Long storeId) {
        PosStoreLinePay current = getCurrent(storeId);
        if (current == null || !VERIFIED.equals(current.getCredentialStatus())
                || !Integer.valueOf(1).equals(current.getIsEnabled())) {
            return null;
        }
        return current;
    }

    @Override
    public PosStoreLinePay getById(Long credentialId) {
        return credentialId == null ? null : credentialMapper.selectById(credentialId);
    }

    @Override
    public List<PosStoreLinePayVo> selectStoreList(PosStoreLinePayVo query) {
        return credentialMapper.selectLinePayStoreList(query == null ? new PosStoreLinePayVo() : query);
    }

    @Override
    public PosStoreLinePayVo selectStoreDetail(Long storeId) {
        if (storeId == null) {
            throw new ServiceException("LINE Pay store is required");
        }
        PosStoreLinePayVo detail = credentialMapper.selectLinePayStoreDetail(storeId);
        if (detail == null) {
            throw new ServiceException("LINE Pay store not found");
        }
        return detail;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PosStoreLinePay saveVerifiedCredential(StoreLinePayCredentialDto dto, String environment,
                                                  String returnCode, String returnMessage) {
        validate(dto, environment);
        credentialMapper.lockStore(dto.getStoreId());
        PosStoreLinePay current = credentialMapper.selectCurrentByStoreId(dto.getStoreId());
        if (sameCredential(current, dto, environment)) {
            if (!Integer.valueOf(1).equals(current.getIsEnabled())) {
                setCurrentEnabled(dto.getStoreId(), true);
                current.setIsEnabled(1);
            }
            return current;
        }

        Date now = new Date();
        if (current != null) {
            int cleared = credentialMapper.clearCurrent(current.getId(), current.getStoreId(), now);
            if (cleared != 1) {
                throw new ServiceException("LINE Pay credential changed concurrently");
            }
        }

        Integer maxVersion = credentialMapper.selectMaxVersionByStoreId(dto.getStoreId());
        PosStoreLinePay row = new PosStoreLinePay();
        row.setStoreId(dto.getStoreId());
        row.setCredentialVersion(maxVersion == null ? 1 : maxVersion + 1);
        row.setChannelId(dto.getChannelId().trim());
        row.setChannelSecret(dto.getChannelSecret());
        row.setEnvironment(environment.trim().toUpperCase());
        row.setCredentialStatus(VERIFIED);
        row.setIsEnabled(1);
        row.setCurrentStoreId(dto.getStoreId());
        row.setVerifyReturnCode(truncate(returnCode, 8));
        row.setVerifyReturnMessage(truncate(returnMessage, 255));
        row.setVerifiedTime(now);
        row.setCreateTime(now);
        row.setUpdateTime(now);
        row.setCreateBy(currentUser());
        row.setUpdateBy(row.getCreateBy());
        credentialMapper.insert(row);
        return row;
    }

    @Override
    public int setCurrentEnabled(Long storeId, boolean enabled) {
        PosStoreLinePay current = getCurrent(storeId);
        if (current == null || !VERIFIED.equals(current.getCredentialStatus())) {
            throw new ServiceException("LINE Pay credential not found");
        }
        Date now = new Date();
        return credentialMapper.update(null, Wrappers.<PosStoreLinePay>lambdaUpdate()
                .eq(PosStoreLinePay::getId, current.getId())
                .eq(PosStoreLinePay::getCurrentStoreId, storeId)
                .set(PosStoreLinePay::getIsEnabled, enabled ? 1 : 0)
                .set(PosStoreLinePay::getUpdateTime, now)
                .set(PosStoreLinePay::getUpdateBy, currentUser()));
    }

    private static void validate(StoreLinePayCredentialDto dto, String environment) {
        if (dto == null || dto.getStoreId() == null) {
            throw new ServiceException("LINE Pay store is required");
        }
        if (StrUtil.isBlank(dto.getChannelId()) || StrUtil.isBlank(dto.getChannelSecret())) {
            throw new ServiceException("LINE Pay credential is required");
        }
        if (StrUtil.isBlank(environment)) {
            throw new ServiceException("LINE Pay environment is required");
        }
    }

    private static boolean sameCredential(PosStoreLinePay current, StoreLinePayCredentialDto dto,
                                          String environment) {
        return current != null
                && current.getChannelId().equals(dto.getChannelId().trim())
                && current.getChannelSecret().equals(dto.getChannelSecret())
                && current.getEnvironment().equalsIgnoreCase(environment.trim());
    }

    private static String truncate(String value, int maxLength) {
        if (value == null) {
            return null;
        }
        return value.length() <= maxLength ? value : value.substring(0, maxLength);
    }

    private static String currentUser() {
        try {
            return SecurityUtils.getUsername();
        } catch (Exception ignored) {
            return "system";
        }
    }
}
