package com.ruoyi.web.modules.entry.service.impl;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.web.modules.account.domain.BizMember;
import com.ruoyi.web.modules.account.mapper.BizMemberMapper;
import com.ruoyi.web.modules.content.facade.IMerchantEntryAgreementFacade;
import com.ruoyi.web.modules.entry.constant.MerchantEntryApplyConstants;
import com.ruoyi.web.modules.entry.domain.BizMerchantEntryApply;
import com.ruoyi.web.modules.entry.dto.EntryApplyApproveDTO;
import com.ruoyi.web.modules.entry.dto.EntryApplyRejectDTO;
import com.ruoyi.web.modules.entry.dto.EntryApplyShopDTO;
import com.ruoyi.web.modules.entry.dto.EntryApplySubmitDTO;
import com.ruoyi.web.modules.entry.mapper.BizMerchantEntryApplyMapper;
import com.ruoyi.web.modules.entry.service.IMerchantEntryApplyService;
import java.util.Calendar;
import com.ruoyi.web.modules.entry.support.EntryApplyFormMapper;
import com.ruoyi.web.modules.entry.support.EntryApplyFormValidator;
import com.ruoyi.web.modules.entry.support.MemberEntryMessageSupport;
import com.ruoyi.web.modules.entry.vo.EntryApplyContextVO;
import com.ruoyi.web.modules.entry.vo.EntryApplyDetailVO;
import com.ruoyi.web.modules.entry.vo.EntryApplyListVO;
import com.ruoyi.web.modules.entry.vo.EntryApplyMyVO;
import com.ruoyi.web.modules.finance.service.IShopFundService;
import com.ruoyi.web.modules.merchant.constant.MerchantConstants;
import com.ruoyi.web.modules.merchant.domain.BizMerchant;
import com.ruoyi.web.modules.merchant.dto.MerchantCreateDTO;
import com.ruoyi.web.modules.merchant.mapper.BizMerchantMapper;
import com.ruoyi.web.modules.merchant.service.IMerchantAccountBindService;
import com.ruoyi.web.modules.merchant.support.BizCompleteEvaluator;
import com.ruoyi.web.modules.merchant.vo.MerchantShopBriefVO;
import com.ruoyi.web.modules.store.dto.ShopCreateDTO;
import com.ruoyi.web.modules.store.domain.BizMerchantAccount;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.mapper.BizMerchantAccountMapper;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;
import com.ruoyi.web.modules.store.service.IShopService;

@Service
public class MerchantEntryApplyServiceImpl implements IMerchantEntryApplyService
{
    private static final Logger log = LoggerFactory.getLogger(MerchantEntryApplyServiceImpl.class);

    @Autowired
    private BizMerchantEntryApplyMapper applyMapper;

    @Autowired
    private BizMerchantMapper merchantMapper;

    @Autowired
    private BizShopMapper shopMapper;

    @Autowired
    private BizMerchantAccountMapper merchantAccountMapper;

    @Autowired
    private BizMemberMapper memberMapper;

    @Autowired
    private IMerchantEntryAgreementFacade agreementFacade;

    @Autowired
    private IMerchantAccountBindService accountBindService;

    @Autowired
    private IShopService shopService;

    @Autowired
    private MemberEntryMessageSupport messageSupport;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private IShopFundService shopFundService;

    @Override
    public EntryApplyContextVO resolveEntryContext(Long memberId)
    {
        EntryApplyContextVO vo = new EntryApplyContextVO();
        vo.setShopOnlyMode(false);
        vo.setCanShopOnlyApply(false);
        BizMerchant merchant = findMerchantByMemberAdmin(memberId);
        if (merchant == null)
        {
            return vo;
        }
        vo.setShopOnlyMode(true);
        vo.setMerchantId(merchant.getMerchantId());
        vo.setMerchantType(merchant.getMerchantType());
        vo.setSubjectLabel(EntryApplyFormMapper.resolveSubjectLabel(merchant.getMerchantType(), merchant));
        vo.setMerchantName(StringUtils.isNotEmpty(merchant.getMerchantName()) ? merchant.getMerchantName()
                : vo.getSubjectLabel());
        EntryApplySubmitDTO template = EntryApplyFormMapper.buildSubmitFromExistingMerchant(merchant, null, false);
        vo.setSubject(template.getSubject());
        vo.setBiz(template.getBiz());
        String blockReason = resolveShopOnlyBlockReason(merchant);
        if (StringUtils.isEmpty(blockReason))
        {
            vo.setCanShopOnlyApply(true);
        }
        else
        {
            vo.setBlockReason(blockReason);
        }
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitApply(Long memberId, EntryApplySubmitDTO dto)
    {
        agreementFacade.assertAccepted(Boolean.TRUE.equals(dto.getAgreementAccepted()));
        if (applyMapper.countBlockingByMemberId(memberId) > 0)
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_PENDING_EXISTS);
        }
        BizMember member = requireMember(memberId);
        if (StringUtils.isEmpty(member.getNickName()))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_MEMBER_NICK_REQUIRED);
        }

        BizMerchant existingMerchant = findMerchantByMemberAdmin(memberId);
        boolean shopOnlyApply = existingMerchant != null;
        EntryApplySubmitDTO submitDto = dto;
        Long existingMerchantId = null;
        String merchantType;

        if (shopOnlyApply)
        {
            assertMerchantEligibleForShopOnly(existingMerchant);
            EntryApplyFormValidator.validateShopOnlySubmit(dto, existingMerchant.getMerchantType());
            submitDto = EntryApplyFormMapper.buildSubmitFromExistingMerchant(existingMerchant, dto.getShop(),
                    dto.getAgreementAccepted());
            existingMerchantId = existingMerchant.getMerchantId();
            merchantType = existingMerchant.getMerchantType();
            assertUniqueOnSubmit(submitDto, merchantType, null, existingMerchantId);
        }
        else
        {
            EntryApplyFormValidator.validateMobileSubmit(dto);
            merchantType = EntryApplyFormValidator.resolveMerchantType(dto);
            assertUniqueOnSubmit(dto, merchantType, null, null);
        }

        Date now = new Date();
        BizMerchantEntryApply apply = new BizMerchantEntryApply();
        apply.setApplyNo(generateApplyNo());
        apply.setApplyStatus(MerchantEntryApplyConstants.STATUS_PENDING);
        apply.setMerchantType(merchantType);
        apply.setMemberId(memberId);
        apply.setMemberCode(StringUtils.isNotEmpty(member.getMemberCode()) ? member.getMemberCode() : member.getNickName());
        apply.setSubjectLabel(EntryApplyFormMapper.resolveSubjectLabel(merchantType, submitDto.getSubject()));
        apply.setContactName(resolveContactName(submitDto, merchantType));
        apply.setContactPhone(resolveContactPhone(submitDto, merchantType));
        apply.setIdCardNo(resolveIdCardNo(submitDto, merchantType));
        apply.setCreditCode(resolveCreditCode(submitDto, merchantType));
        apply.setMerchantName(resolveListMerchantName(submitDto, merchantType));
        apply.setShopName(submitDto.getShop().getShopName());
        apply.setFormJson(EntryApplyFormMapper.toFormJson(submitDto, shopOnlyApply, existingMerchantId));
        apply.setApplyTime(now);
        apply.setDelFlag(MerchantEntryApplyConstants.DEL_NORMAL);
        apply.setCreateBy(String.valueOf(memberId));
        applyMapper.insert(apply);

        Map<String, Object> data = new HashMap<>();
        data.put("applyId", apply.getApplyId());
        data.put("applyNo", apply.getApplyNo());
        data.put("applyStatus", apply.getApplyStatus());
        return data;
    }

    @Override
    public List<EntryApplyMyVO> listMyApplies(Long memberId)
    {
        List<EntryApplyMyVO> rows = new ArrayList<>();
        for (BizMerchantEntryApply apply : applyMapper.selectMyByMemberId(memberId,
                MerchantEntryApplyConstants.MY_APPLY_LIMIT))
        {
            EntryApplyMyVO vo = new EntryApplyMyVO();
            vo.setApplyId(apply.getApplyId());
            vo.setApplyNo(apply.getApplyNo());
            vo.setApplyStatus(apply.getApplyStatus());
            vo.setApplyTime(apply.getApplyTime());
            vo.setAuditTime(apply.getAuditTime());
            vo.setRejectReason(apply.getRejectReason());
            vo.setMerchantId(apply.getMerchantId());
            vo.setShopId(apply.getShopId());
            vo.setPublicityStartTime(apply.getPublicityStartTime());
            vo.setPublicityEndTime(apply.getPublicityEndTime());
            rows.add(vo);
        }
        return rows;
    }

    @Override
    public List<EntryApplyListVO> selectList(BizMerchantEntryApply query)
    {
        return applyMapper.selectList(query);
    }

    @Override
    public int countPlatformPending()
    {
        return applyMapper.countPlatformPending();
    }

    @Override
    public EntryApplyDetailVO selectDetail(Long applyId)
    {
        BizMerchantEntryApply apply = requirePendingOrAny(applyId);
        EntryApplyDetailVO vo = new EntryApplyDetailVO();
        vo.setApplyId(apply.getApplyId());
        vo.setApplyNo(apply.getApplyNo());
        vo.setApplyStatus(apply.getApplyStatus());
        vo.setMerchantType(apply.getMerchantType());
        vo.setMemberId(apply.getMemberId());
        vo.setMemberCode(apply.getMemberCode());
        vo.setSubjectLabel(apply.getSubjectLabel());
        vo.setContactName(apply.getContactName());
        vo.setContactPhone(apply.getContactPhone());
        vo.setRejectReason(apply.getRejectReason());
        vo.setMerchantId(apply.getMerchantId());
        vo.setShopId(apply.getShopId());
        vo.setApplyTime(apply.getApplyTime());
        vo.setAuditBy(apply.getAuditBy());
        vo.setAuditTime(apply.getAuditTime());
        vo.setPublicityStartTime(apply.getPublicityStartTime());
        vo.setPublicityEndTime(apply.getPublicityEndTime());
        EntryApplyFormMapper.ParsedForm parsed = EntryApplyFormMapper.parseFormJson(apply.getFormJson());
        vo.setSubject(parsed.getSubject());
        vo.setBiz(parsed.getBiz());
        vo.setShop(parsed.getShop());
        vo.setAgreementAccepted(parsed.getAgreementAccepted());
        boolean pending = MerchantEntryApplyConstants.STATUS_PENDING.equals(apply.getApplyStatus());
        vo.setCanApprove(pending);
        vo.setCanReject(pending);
        vo.setCanCompletePublicity(isPublicityReady(apply));
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> approve(Long applyId, EntryApplyApproveDTO dto, String operator)
    {
        BizMerchantEntryApply apply = requirePendingApply(applyId);
        assertUniqueOnSubmit(buildSubmitFromApply(apply), apply.getMerchantType(), apply.getApplyId(),
                EntryApplyFormMapper.resolveExistingMerchantId(apply.getFormJson()));

        requireMember(apply.getMemberId());

        Date now = new Date();
        Date publicityEnd = calcPublicityEnd(now);

        BizMerchantEntryApply update = new BizMerchantEntryApply();
        update.setApplyId(apply.getApplyId());
        update.setApplyStatus(MerchantEntryApplyConstants.STATUS_PUBLICITY);
        update.setPublicityStartTime(now);
        update.setPublicityEndTime(publicityEnd);
        update.setAuditBy(operator);
        update.setAuditTime(now);
        update.setUpdateBy(operator);
        applyMapper.updateAuditResult(update);

        messageSupport.sendPublicity(apply.getMemberId(), apply.getApplyId(), apply.getApplyNo(), publicityEnd);

        Map<String, Object> data = new HashMap<>();
        data.put("applyStatus", MerchantEntryApplyConstants.STATUS_PUBLICITY);
        data.put("publicityEndTime", publicityEnd);
        return data;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> completePublicity(Long applyId, String operator)
    {
        BizMerchantEntryApply apply = requirePublicityApply(applyId);
        if (!isPublicityReady(apply))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_PUBLICITY_NOT_END);
        }
        return finalizeEntry(apply, operator);
    }

    @Override
    public int processExpiredPublicity()
    {
        int count = 0;
        for (BizMerchantEntryApply apply : applyMapper.selectPublicityExpiredList())
        {
            try
            {
                completePublicity(apply.getApplyId(), "system");
                count++;
            }
            catch (Exception e)
            {
                log.error("入驻公示自动建档失败，applyId={}", apply.getApplyId(), e);
            }
        }
        return count;
    }

    private Map<String, Object> finalizeEntry(BizMerchantEntryApply apply, String operator)
    {
        if (apply.getMerchantId() != null && apply.getShopId() != null)
        {
            return markApplyApproved(apply, apply.getMerchantId(), apply.getShopId(), operator, false);
        }

        EntryApplySubmitDTO submitDto = buildSubmitFromApply(apply);

        BizMerchant memberAdminMerchant = findMerchantByMemberAdmin(apply.getMemberId());
        if (memberAdminMerchant != null)
        {
            log.info("会员已是商户管理员，复用商户并按申请创建店铺完成入驻，applyId={}，merchantId={}",
                    apply.getApplyId(), memberAdminMerchant.getMerchantId());
            return reconcileWithExistingMerchant(apply, memberAdminMerchant, submitDto, operator, true);
        }

        BizMerchant existingMerchant = findExistingMerchant(submitDto, apply.getMerchantType());
        if (existingMerchant != null && canReconcileExistingMerchant(existingMerchant, apply.getMemberId()))
        {
            log.info("入驻申请关联已有商户建档，applyId={}，merchantId={}", apply.getApplyId(),
                    existingMerchant.getMerchantId());
            return reconcileWithExistingMerchant(apply, existingMerchant, submitDto, operator, false);
        }

        assertUniqueOnSubmit(submitDto, apply.getMerchantType(), apply.getApplyId(),
                EntryApplyFormMapper.resolveExistingMerchantId(apply.getFormJson()));
        EntryApplyFormValidator.validateMobileSubmit(submitDto);

        BizMember member = requireMember(apply.getMemberId());
        if (StringUtils.isEmpty(member.getNickName()))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_MEMBER_NICK_REQUIRED);
        }

        BizMerchant merchant = EntryApplyFormMapper.toMerchantFromJson(apply.getFormJson(), apply.getMerchantType());
        String bizComplete = BizCompleteEvaluator.evaluateCompleteFlag(merchant);
        if (!MerchantConstants.BIZ_COMPLETE.equals(bizComplete))
        {
            throw new ServiceException("经营信息不完整，无法完成入驻："
                    + String.join("、", BizCompleteEvaluator.pendingFields(merchant)));
        }

        Date now = new Date();
        merchant.setCertStatus(MerchantConstants.CERT_NORMAL);
        merchant.setCertTime(now);
        merchant.setBizComplete(bizComplete);
        merchant.setShopCount(0);
        merchant.setDelFlag(MerchantConstants.DEL_FLAG_NORMAL);
        merchant.setCreateBy(operator);
        merchantMapper.insert(merchant);

        MerchantCreateDTO bindDto = EntryApplyFormMapper.toMerchantCreateDTO(merchant, apply.getMemberId());
        accountBindService.bindAccountOnMerchantCreate(merchant.getMerchantId(), bindDto, operator);

        ShopCreateDTO shopDto = EntryApplyFormMapper.toShopCreateDTO(apply.getFormJson(), merchant.getMerchantId());
        Long shopId = shopService.createShop(shopDto, operator);

        shopFundService.seedContactBankAccountFromEntry(shopId, merchant.getContactName(),
                merchant.getBankAccount(), operator);

        return markApplyApproved(apply, merchant.getMerchantId(), shopId, operator, true);
    }

    private Map<String, Object> markApplyApproved(BizMerchantEntryApply apply, Long merchantId, Long shopId,
            String operator, boolean notifyMember)
    {
        BizMerchantEntryApply update = new BizMerchantEntryApply();
        update.setApplyId(apply.getApplyId());
        update.setApplyStatus(MerchantEntryApplyConstants.STATUS_APPROVED);
        update.setMerchantId(merchantId);
        update.setShopId(shopId);
        update.setUpdateBy(operator);
        applyMapper.updateAuditResult(update);

        if (notifyMember)
        {
            messageSupport.sendPass(apply.getMemberId(), apply.getApplyId(), apply.getApplyNo());
        }

        Map<String, Object> data = new HashMap<>();
        data.put("merchantId", merchantId);
        data.put("shopId", shopId);
        return data;
    }

    private Map<String, Object> reconcileWithExistingMerchant(BizMerchantEntryApply apply, BizMerchant merchant,
            EntryApplySubmitDTO submitDto, String operator, boolean createShopFromApply)
    {
        requireMember(apply.getMemberId());
        if (EntryApplyFormMapper.isShopOnlyApply(apply.getFormJson()))
        {
            EntryApplyFormValidator.validateShopOnlySubmit(submitDto, merchant.getMerchantType());
        }
        else
        {
            EntryApplyFormValidator.validateMobileSubmit(submitDto);
        }

        BizMerchantAccount account = merchantAccountMapper.selectByMerchantId(merchant.getMerchantId());
        if (account == null)
        {
            MerchantCreateDTO bindDto = EntryApplyFormMapper.toMerchantCreateDTO(merchant, apply.getMemberId());
            accountBindService.bindAccountOnMerchantCreate(merchant.getMerchantId(), bindDto, operator);
        }

        Long shopId = resolveShopForReconcile(apply, merchant, submitDto, operator, createShopFromApply);
        shopFundService.seedContactBankAccountFromEntry(shopId, merchant.getContactName(),
                merchant.getBankAccount(), operator);

        return markApplyApproved(apply, merchant.getMerchantId(), shopId, operator, true);
    }

    private Long resolveShopForReconcile(BizMerchantEntryApply apply, BizMerchant merchant,
            EntryApplySubmitDTO submitDto, String operator, boolean createShopFromApply)
    {
        if (apply.getShopId() != null)
        {
            return apply.getShopId();
        }
        String shopName = submitDto.getShop().getShopName();
        if (createShopFromApply)
        {
            Long matchedShopId = findShopIdByNameUnderMerchant(merchant.getMerchantId(), shopName);
            if (matchedShopId != null)
            {
                return matchedShopId;
            }
            if (shopMapper.countByShopName(shopName, null) > 0)
            {
                throw new ServiceException("店铺名称已存在");
            }
            ShopCreateDTO shopDto = EntryApplyFormMapper.toShopCreateDTO(apply.getFormJson(), merchant.getMerchantId());
            return shopService.createShop(shopDto, operator);
        }

        BizShop earliest = shopMapper.selectEarliestByMerchantId(merchant.getMerchantId());
        if (earliest != null)
        {
            return earliest.getShopId();
        }
        if (shopMapper.countByShopName(shopName, null) > 0)
        {
            throw new ServiceException("店铺名称已存在");
        }
        ShopCreateDTO shopDto = EntryApplyFormMapper.toShopCreateDTO(apply.getFormJson(), merchant.getMerchantId());
        return shopService.createShop(shopDto, operator);
    }

    private Long findShopIdByNameUnderMerchant(Long merchantId, String shopName)
    {
        if (StringUtils.isEmpty(shopName))
        {
            return null;
        }
        for (MerchantShopBriefVO shop : shopMapper.selectBriefByMerchantId(merchantId))
        {
            if (shopName.equals(shop.getShopName()))
            {
                return shop.getShopId();
            }
        }
        return null;
    }

    private BizMerchant findMerchantByMemberAdmin(Long memberId)
    {
        if (memberId == null)
        {
            return null;
        }
        BizMerchantAccount account = merchantAccountMapper.selectMerchantAdminByUserId(memberId);
        if (account == null || account.getMerchantId() == null)
        {
            return null;
        }
        return merchantMapper.selectById(account.getMerchantId());
    }

    private BizMerchant findExistingMerchant(EntryApplySubmitDTO dto, String merchantType)
    {
        if (MerchantConstants.TYPE_PERSON.equals(merchantType) && dto.getSubject() != null
                && StringUtils.isNotEmpty(dto.getSubject().getIdCardNo()))
        {
            return merchantMapper.selectByIdCardNo(dto.getSubject().getIdCardNo());
        }
        if (MerchantConstants.TYPE_ENTERPRISE.equals(merchantType) && dto.getSubject() != null
                && StringUtils.isNotEmpty(dto.getSubject().getCreditCode()))
        {
            return merchantMapper.selectByCreditCode(dto.getSubject().getCreditCode());
        }
        return null;
    }

    private boolean canReconcileExistingMerchant(BizMerchant merchant, Long memberId)
    {
        BizMerchantAccount account = merchantAccountMapper.selectByMerchantId(merchant.getMerchantId());
        if (account == null)
        {
            return true;
        }
        return memberId != null && memberId.equals(account.getAccountId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reject(EntryApplyRejectDTO dto, String operator)
    {
        if (dto == null || dto.getApplyId() == null)
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_APPLY_NOT_FOUND);
        }
        if (StringUtils.isEmpty(dto.getRejectReason()))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_REJECT_REASON_REQUIRED);
        }
        BizMerchantEntryApply apply = requirePendingApply(dto.getApplyId());
        Date now = new Date();
        BizMerchantEntryApply update = new BizMerchantEntryApply();
        update.setApplyId(apply.getApplyId());
        update.setApplyStatus(MerchantEntryApplyConstants.STATUS_REJECTED);
        update.setRejectReason(dto.getRejectReason().trim());
        update.setAuditBy(operator);
        update.setAuditTime(now);
        update.setUpdateBy(operator);
        applyMapper.updateAuditResult(update);
        messageSupport.sendReject(apply.getMemberId(), apply.getApplyId(), apply.getApplyNo(), dto.getRejectReason());
    }

    private BizMerchantEntryApply requirePendingApply(Long applyId)
    {
        BizMerchantEntryApply apply = applyMapper.selectById(applyId);
        if (apply == null)
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_APPLY_NOT_FOUND);
        }
        if (!MerchantEntryApplyConstants.STATUS_PENDING.equals(apply.getApplyStatus()))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_NOT_PENDING);
        }
        return apply;
    }

    private BizMerchantEntryApply requirePendingOrAny(Long applyId)
    {
        BizMerchantEntryApply apply = applyMapper.selectById(applyId);
        if (apply == null)
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_APPLY_NOT_FOUND);
        }
        return apply;
    }

    private BizMember requireMember(Long memberId)
    {
        BizMember member = memberMapper.selectById(memberId);
        if (member == null)
        {
            throw new ServiceException("会员不存在");
        }
        return member;
    }


    private String resolveContactName(EntryApplySubmitDTO dto, String merchantType)
    {
        if (MerchantConstants.TYPE_ENTERPRISE.equals(merchantType))
        {
            return dto.getSubject().getContactName();
        }
        return dto.getSubject().getContactName();
    }

    private String resolveContactPhone(EntryApplySubmitDTO dto, String merchantType)
    {
        return dto.getSubject().getContactPhone();
    }

    private String resolveListMerchantName(EntryApplySubmitDTO dto, String merchantType)
    {
        if (MerchantConstants.TYPE_PERSON.equals(merchantType))
        {
            return dto.getBiz().getMerchantName();
        }
        return StringUtils.isNotEmpty(dto.getBiz().getMerchantName())
                ? dto.getBiz().getMerchantName() : dto.getSubject().getCompanyName();
    }

    private BizMerchantEntryApply requirePublicityApply(Long applyId)
    {
        BizMerchantEntryApply apply = applyMapper.selectById(applyId);
        if (apply == null)
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_APPLY_NOT_FOUND);
        }
        if (!MerchantEntryApplyConstants.STATUS_PUBLICITY.equals(apply.getApplyStatus()))
        {
            throw new ServiceException(MerchantEntryApplyConstants.MSG_NOT_PUBLICITY);
        }
        return apply;
    }

    private boolean isPublicityReady(BizMerchantEntryApply apply)
    {
        if (!MerchantEntryApplyConstants.STATUS_PUBLICITY.equals(apply.getApplyStatus()))
        {
            return false;
        }
        return apply.getPublicityEndTime() != null && !apply.getPublicityEndTime().after(new Date());
    }

    private Date calcPublicityEnd(Date start)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTime(start);
        cal.add(Calendar.DAY_OF_MONTH, resolvePublicityDays());
        return cal.getTime();
    }

    private int resolvePublicityDays()
    {
        String days = configService.selectConfigByKey("agri.merchant.entry.publicityDays");
        if (StringUtils.isNotEmpty(days))
        {
            try
            {
                return Integer.parseInt(days);
            }
            catch (NumberFormatException ignored)
            {
                // use default
            }
        }
        return MerchantEntryApplyConstants.DEFAULT_PUBLICITY_DAYS;
    }

    private void assertUniqueOnSubmit(EntryApplySubmitDTO dto, String merchantType, Long excludeApplyId,
            Long existingMerchantId)
    {
        BizMerchant existingMerchant = existingMerchantId != null ? merchantMapper.selectById(existingMerchantId) : null;
        if (MerchantConstants.TYPE_ENTERPRISE.equals(merchantType))
        {
            String creditCode = dto.getSubject().getCreditCode();
            if (!isSameCreditCode(existingMerchant, creditCode)
                    && merchantMapper.countByCreditCode(creditCode, null) > 0)
            {
                throw new ServiceException("该统一社会信用代码已入驻");
            }
            if (!isSameCreditCode(existingMerchant, creditCode)
                    && applyMapper.countByCreditCodeActive(creditCode, excludeApplyId) > 0)
            {
                throw new ServiceException("该统一社会信用代码已有入驻申请");
            }
        }
        if (MerchantConstants.TYPE_PERSON.equals(merchantType))
        {
            String idCardNo = dto.getSubject().getIdCardNo();
            if (StringUtils.isNotEmpty(idCardNo))
            {
                if (!isSameIdCardNo(existingMerchant, idCardNo)
                        && merchantMapper.countByIdCardNo(idCardNo, null) > 0)
                {
                    throw new ServiceException("该证件号码已入驻");
                }
                if (!isSameIdCardNo(existingMerchant, idCardNo)
                        && applyMapper.countByIdCardNoActive(idCardNo, excludeApplyId) > 0)
                {
                    throw new ServiceException("该证件号码已有入驻申请");
                }
            }
        }
        String merchantName = resolveListMerchantName(dto, merchantType);
        if (StringUtils.isNotEmpty(merchantName))
        {
            if (!isSameMerchantName(existingMerchant, merchantName)
                    && merchantMapper.countByMerchantName(merchantName, null) > 0)
            {
                throw new ServiceException("商户名称已存在");
            }
            if (!isSameMerchantName(existingMerchant, merchantName)
                    && applyMapper.countByMerchantNameActive(merchantName, excludeApplyId) > 0)
            {
                throw new ServiceException("商户名称已有入驻申请");
            }
        }
        String shopName = dto.getShop().getShopName();
        if (shopMapper.countByShopName(shopName, null) > 0)
        {
            throw new ServiceException("店铺名称已存在");
        }
        if (applyMapper.countByShopNameActive(shopName, excludeApplyId) > 0)
        {
            throw new ServiceException("店铺名称已有入驻申请");
        }
    }

    private void assertMerchantEligibleForShopOnly(BizMerchant merchant)
    {
        String reason = resolveShopOnlyBlockReason(merchant);
        if (StringUtils.isNotEmpty(reason))
        {
            throw new ServiceException(reason);
        }
    }

    private String resolveShopOnlyBlockReason(BizMerchant merchant)
    {
        if (merchant == null)
        {
            return null;
        }
        if (MerchantConstants.CERT_FROZEN.equals(merchant.getCertStatus()))
        {
            return MerchantEntryApplyConstants.MSG_MERCHANT_FROZEN;
        }
        if (MerchantConstants.CERT_CANCELLED.equals(merchant.getCertStatus()))
        {
            return MerchantEntryApplyConstants.MSG_MERCHANT_CANCELLED;
        }
        if (!MerchantConstants.BIZ_COMPLETE.equals(merchant.getBizComplete()))
        {
            return MerchantEntryApplyConstants.MSG_EXISTING_MERCHANT_NOT_READY;
        }
        return null;
    }

    private boolean isSameCreditCode(BizMerchant merchant, String creditCode)
    {
        return merchant != null && StringUtils.isNotEmpty(creditCode)
                && creditCode.equals(merchant.getCreditCode());
    }

    private boolean isSameIdCardNo(BizMerchant merchant, String idCardNo)
    {
        return merchant != null && StringUtils.isNotEmpty(idCardNo)
                && idCardNo.equals(merchant.getIdCardNo());
    }

    private boolean isSameMerchantName(BizMerchant merchant, String merchantName)
    {
        return merchant != null && StringUtils.isNotEmpty(merchantName)
                && merchantName.equals(merchant.getMerchantName());
    }

    private EntryApplySubmitDTO buildSubmitFromApply(BizMerchantEntryApply apply)
    {
        EntryApplyFormMapper.ParsedForm parsed = EntryApplyFormMapper.parseFormJson(apply.getFormJson());
        EntryApplySubmitDTO dto = new EntryApplySubmitDTO();
        dto.setSubject(parsed.getSubject());
        dto.setBiz(parsed.getBiz());
        dto.setShop(parsed.getShop());
        return dto;
    }

    private String resolveIdCardNo(EntryApplySubmitDTO dto, String merchantType)
    {
        if (MerchantConstants.TYPE_PERSON.equals(merchantType) && dto.getSubject() != null)
        {
            return dto.getSubject().getIdCardNo();
        }
        return null;
    }

    private String resolveCreditCode(EntryApplySubmitDTO dto, String merchantType)
    {
        if (MerchantConstants.TYPE_ENTERPRISE.equals(merchantType) && dto.getSubject() != null)
        {
            return dto.getSubject().getCreditCode();
        }
        return null;
    }

    private String generateApplyNo()
    {
        return MerchantEntryApplyConstants.APPLY_NO_PREFIX
                + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
                + String.format("%03d", (int) (Math.random() * 1000));
    }
}
