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.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.store.dto.ShopCreateDTO;
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 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
    @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);
        }
        EntryApplyFormValidator.validateMobileSubmit(dto);
        String merchantType = EntryApplyFormValidator.resolveMerchantType(dto);
        assertUniqueOnSubmit(dto, merchantType, 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, dto.getSubject()));
        apply.setContactName(resolveContactName(dto, merchantType));
        apply.setContactPhone(resolveContactPhone(dto, merchantType));
        apply.setIdCardNo(resolveIdCardNo(dto, merchantType));
        apply.setCreditCode(resolveCreditCode(dto, merchantType));
        apply.setMerchantName(resolveListMerchantName(dto, merchantType));
        apply.setShopName(dto.getShop().getShopName());
        apply.setFormJson(EntryApplyFormMapper.toFormJson(dto));
        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());

        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)
    {
        EntryApplySubmitDTO submitDto = buildSubmitFromApply(apply);
        assertUniqueOnSubmit(submitDto, apply.getMerchantType(), apply.getApplyId());
        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);

        BizMerchantEntryApply update = new BizMerchantEntryApply();
        update.setApplyId(apply.getApplyId());
        update.setApplyStatus(MerchantEntryApplyConstants.STATUS_APPROVED);
        update.setMerchantId(merchant.getMerchantId());
        update.setShopId(shopId);
        update.setUpdateBy(operator);
        applyMapper.updateAuditResult(update);

        messageSupport.sendPass(apply.getMemberId(), apply.getApplyId(), apply.getApplyNo());

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

    @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)
    {
        if (MerchantConstants.TYPE_ENTERPRISE.equals(merchantType))
        {
            String creditCode = dto.getSubject().getCreditCode();
            if (merchantMapper.countByCreditCode(creditCode, null) > 0)
            {
                throw new ServiceException("该统一社会信用代码已入驻");
            }
            if (applyMapper.countByCreditCodeActive(creditCode, excludeApplyId) > 0)
            {
                throw new ServiceException("该统一社会信用代码已有入驻申请");
            }
        }
        if (MerchantConstants.TYPE_PERSON.equals(merchantType))
        {
            String idCardNo = dto.getSubject().getIdCardNo();
            if (StringUtils.isNotEmpty(idCardNo))
            {
                if (merchantMapper.countByIdCardNo(idCardNo, null) > 0)
                {
                    throw new ServiceException("该证件号码已入驻");
                }
                if (applyMapper.countByIdCardNoActive(idCardNo, excludeApplyId) > 0)
                {
                    throw new ServiceException("该证件号码已有入驻申请");
                }
            }
        }
        String merchantName = resolveListMerchantName(dto, merchantType);
        if (StringUtils.isNotEmpty(merchantName))
        {
            if (merchantMapper.countByMerchantName(merchantName, null) > 0)
            {
                throw new ServiceException("商户名称已存在");
            }
            if (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 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));
    }
}
