package com.jeesharp.business.modules.marketing.coupon.application;

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.coupon.api.MemberCouponLifecycleApi;
import com.jeesharp.business.modules.coupon.api.MemberCouponMobileApi;
import com.jeesharp.business.modules.coupon.dto.MemberCouponOfflineConfirmDTO;
import com.jeesharp.business.modules.coupon.enums.MemberCouponUseStatusEnum;
import com.jeesharp.business.modules.coupon.vo.MemberCouponInfoVO;
import com.jeesharp.business.modules.marketing.application.support.MarketingVerifyTokenSupport;
import com.jeesharp.business.modules.marketing.application.support.MarketingVerifyTokenSupport.MarketingVerifyTokenPayload;
import com.jeesharp.business.modules.marketing.coupon.dto.MarketingCouponAdminVerifyDTO;
import com.jeesharp.business.modules.marketing.coupon.dto.MarketingCouponVerifyDTO;
import com.jeesharp.business.modules.marketing.coupon.dto.MarketingCouponVerifyRecordsQueryDTO;
import com.jeesharp.business.modules.marketing.coupon.vo.MarketingCouponVerifyCodeVO;
import com.jeesharp.business.modules.marketing.coupon.vo.MarketingCouponVerifyResultVO;
import com.jeesharp.business.modules.marketing.couponVerifyLog.convert.MemberCouponVerifyLogConvert;
import com.jeesharp.business.modules.marketing.couponVerifyLog.entity.MemberCouponVerifyLog;
import com.jeesharp.business.modules.marketing.couponVerifyLog.enums.CouponVerifyTypeEnum;
import com.jeesharp.business.modules.marketing.couponVerifyLog.service.MemberCouponVerifyLogService;
import com.jeesharp.business.modules.marketing.couponVerifyLog.vo.MemberCouponVerifyLogListVO;
import com.jeesharp.business.modules.marketing.couponVerifyLog.vo.MemberCouponVerifyLogQueryVO;
import com.jeesharp.business.modules.marketing.memberTenant.enums.MemberTenantBindStatusEnum;
import com.jeesharp.business.modules.marketing.memberTenant.enums.MemberTenantRoleEnum;
import com.jeesharp.business.modules.marketing.memberTenant.service.MemberTenantService;
import com.jeesharp.business.modules.marketing.tenant.mapper.MarketingTenantReadMapper;
import com.jeesharp.business.modules.marketing.tenant.vo.MarketingTenantBriefVO;
import com.jeesharp.business.modules.member.entity.Member;
import com.jeesharp.business.modules.member.service.MemberService;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import com.jeesharp.framework.utils.WebFrameworkUtils;
import lombok.AllArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * 店铺券动态码与店员扫码核销编排。
 *
 * @author jeesharp
 * @since 2026-06-25
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class MarketingCouponVerifyApplicationService {

    private final MemberCouponMobileApi memberCouponMobileApi;
    private final MemberCouponLifecycleApi memberCouponLifecycleApi;
    private final MemberCouponVerifyLogService memberCouponVerifyLogService;
    private final MemberTenantService memberTenantService;
    private final MarketingTenantReadMapper marketingTenantReadMapper;
    private final MarketingVerifyTokenSupport marketingVerifyTokenSupport;
    private final MemberService memberService;

    public MarketingCouponVerifyCodeVO createVerifyCode(String memberId, String tenantCode, String memberCouponId) {
        MarketingTenantBriefVO tenant = requireTenant(tenantCode);
        assertMemberBound(memberId, tenant.getId());
        MemberCouponInfoVO coupon = memberCouponMobileApi.getStoreOfflineMemberCouponInfo(memberId, tenant.getId(), memberCouponId);
        if (!MemberCouponUseStatusEnum.UNUSED.getValue().equals(coupon.getStatus())) {
            throw new BizErrorException("优惠券不可生成核销码");
        }
        MarketingVerifyTokenPayload payload = marketingVerifyTokenSupport.issue(
                memberCouponId, memberId, tenant.getId(), tenant.getTenantCode());
        MarketingCouponVerifyCodeVO vo = new MarketingCouponVerifyCodeVO();
        vo.setVerifyToken(payload.getVerifyToken());
        vo.setExpireAt(payload.getExpireAt());
        vo.setQrcodeContent(marketingVerifyTokenSupport.buildQrcodeContent(payload));
        return vo;
    }

    @Transactional(readOnly = false)
    public MarketingCouponVerifyResultVO verifyCoupon(String operatorMemberId, MarketingCouponVerifyDTO dto) {
        MarketingTenantBriefVO tenant = requireTenant(dto.getTenantCode());
        assertOperatorIsClerk(operatorMemberId, tenant.getId());
        String operatorName = resolveOperatorName(operatorMemberId);
        return executeVerify(tenant, dto.getVerifyToken(), operatorMemberId, operatorName);
    }

    /**
     * 管理端扫码核销（商家小程序 / PC 运营；不校验 C 端店员角色）。
     */
    @Transactional(readOnly = false)
    public MarketingCouponVerifyResultVO verifyByAdmin(MarketingCouponAdminVerifyDTO dto) {
        if (dto == null || StrUtil.isBlank(dto.getScanContent())) {
            throw new BizErrorException("扫码内容不能为空");
        }
        String scanContent = dto.getScanContent().trim();
        String tenantCode = StrUtil.blankToDefault(
                marketingVerifyTokenSupport.parseTenantCode(scanContent),
                StrUtil.blankToDefault(dto.getTenantCode(), WebFrameworkUtils.getLoginUserTenantCode()));
        if (StrUtil.isBlank(tenantCode)) {
            throw new BizErrorException("无法识别店铺租户，请切换企业后重试");
        }
        String verifyToken = marketingVerifyTokenSupport.parseVerifyToken(scanContent);
        if (StrUtil.isBlank(verifyToken)) {
            throw new BizErrorException("核销令牌无效");
        }
        MarketingTenantBriefVO tenant = requireTenant(tenantCode);
        String operatorId = WebFrameworkUtils.getLoginUserId();
        String operatorName = WebFrameworkUtils.getLoginUserName();
        return executeVerify(tenant, verifyToken, operatorId, operatorName);
    }

    private MarketingCouponVerifyResultVO executeVerify(MarketingTenantBriefVO tenant,
                                                        String rawScanOrToken,
                                                        String operatorId,
                                                        String operatorName) {
        String verifyToken = marketingVerifyTokenSupport.parseVerifyToken(rawScanOrToken);
        if (StrUtil.isBlank(verifyToken)) {
            throw new BizErrorException("核销令牌无效");
        }
        MemberCouponVerifyLog existingLog = memberCouponVerifyLogService.getByVerifyToken(verifyToken);
        if (existingLog != null) {
            return buildResultFromLog(existingLog);
        }
        MarketingVerifyTokenPayload payload = marketingVerifyTokenSupport.get(verifyToken);
        if (payload == null) {
            throw new BizErrorException("核销码已过期，请让顾客刷新后重试");
        }
        if (!StrUtil.equals(tenant.getId(), payload.getTenantId())
                || !StrUtil.equals(tenant.getTenantCode(), payload.getTenantCode())) {
            throw new BizErrorException("核销店铺与券归属不一致");
        }
        MemberCouponInfoVO coupon = memberCouponMobileApi.getStoreOfflineMemberCouponInfo(
                payload.getMemberId(), tenant.getId(), payload.getMemberCouponId());

        MemberCouponOfflineConfirmDTO confirmDTO = new MemberCouponOfflineConfirmDTO();
        confirmDTO.setMemberCouponId(payload.getMemberCouponId());
        confirmDTO.setMemberId(payload.getMemberId());
        confirmDTO.setTenantId(tenant.getId());
        confirmDTO.setTenantCode(tenant.getTenantCode());
        confirmDTO.setVerifyToken(verifyToken);
        confirmDTO.setOperatorId(operatorId);
        confirmDTO.setOperatorName(operatorName);
        memberCouponLifecycleApi.offlineConfirmUsed(confirmDTO);

        LocalDateTime verifyTime = LocalDateTime.now();
        MemberCouponVerifyLog log = new MemberCouponVerifyLog();
        log.setMemberCouponId(payload.getMemberCouponId());
        log.setCouponTemplateId(coupon.getTemplateId());
        log.setCouponName(coupon.getCouponName());
        log.setMemberId(payload.getMemberId());
        log.setVerifyType(CouponVerifyTypeEnum.OFFLINE_SCAN.getValue());
        log.setVerifyTime(verifyTime);
        log.setOperatorId(operatorId);
        log.setOperatorName(operatorName);
        log.setVerifyToken(verifyToken);
        log.setVerifyTenantId(tenant.getId());
        log.setVerifyTenantCode(tenant.getTenantCode());
        log.setTenantId(tenant.getId());
        log.setTenantCode(tenant.getTenantCode());
        try {
            memberCouponVerifyLogService.createVerifyLog(log);
        } catch (DuplicateKeyException ex) {
            MemberCouponVerifyLog dup = memberCouponVerifyLogService.getByVerifyToken(verifyToken);
            if (dup != null) {
                return buildResultFromLog(dup);
            }
            throw ex;
        }
        marketingVerifyTokenSupport.remove(verifyToken);
        return buildResult(coupon, verifyToken, verifyTime);
    }

    public PageRespVO<MemberCouponVerifyLogListVO> pageVerifyRecords(String operatorMemberId,
                                                                     MarketingCouponVerifyRecordsQueryDTO dto) {
        MarketingTenantBriefVO tenant = requireTenant(dto.getTenantCode());
        assertOperatorIsClerk(operatorMemberId, tenant.getId());
        MemberCouponVerifyLogQueryVO query = new MemberCouponVerifyLogQueryVO();
        query.setPageNo(dto.getPageNo());
        query.setPageSize(dto.getPageSize());
        query.setVerifyTimeStart(dto.getVerifyTimeStart());
        query.setVerifyTimeEnd(dto.getVerifyTimeEnd());
        query.setVerifyType(CouponVerifyTypeEnum.OFFLINE_SCAN.getValue());
        query.setVerifyTenantId(tenant.getId());
        Page<MemberCouponVerifyLog> page = new Page<>(query);
        Page<MemberCouponVerifyLog> result = memberCouponVerifyLogService.findPage(page, query);
        PageRespVO<MemberCouponVerifyLogListVO> pageVo = MemberCouponVerifyLogConvert.INSTANCE.toPageRespVO(result);
        memberService.enrichMemberDisplay(pageVo.getRecords());
        maskMobileMemberDisplay(pageVo.getRecords());
        return pageVo;
    }

    private MarketingCouponVerifyResultVO buildResultFromLog(MemberCouponVerifyLog log) {
        MarketingCouponVerifyResultVO vo = new MarketingCouponVerifyResultVO();
        vo.setMemberCouponId(log.getMemberCouponId());
        vo.setCouponName(log.getCouponName());
        vo.setVerifyTime(log.getVerifyTime());
        vo.setVerifyToken(log.getVerifyToken());
        Member member = memberService.get(log.getMemberId());
        if (member != null) {
            vo.setMemberNo(member.getMemberNo());
            vo.setMemberNickname(maskName(member.getRealName(), member.getNickname()));
            vo.setMemberMobile(maskMobile(member.getMobile()));
        }
        return vo;
    }

    private MarketingCouponVerifyResultVO buildResult(MemberCouponInfoVO coupon, String verifyToken, LocalDateTime verifyTime) {
        MarketingCouponVerifyResultVO vo = new MarketingCouponVerifyResultVO();
        vo.setMemberCouponId(coupon.getId());
        vo.setCouponName(coupon.getCouponName());
        vo.setVerifyTime(verifyTime);
        vo.setVerifyToken(verifyToken);
        memberService.enrichMemberDisplay(coupon);
        vo.setMemberNo(coupon.getMemberNo());
        vo.setMemberNickname(maskName(coupon.getMemberNickname(), coupon.getMemberNickname()));
        vo.setMemberMobile(maskMobile(coupon.getMemberMobile()));
        return vo;
    }

    private String resolveOperatorName(String operatorMemberId) {
        Member operator = memberService.get(operatorMemberId);
        if (operator == null) {
            return operatorMemberId;
        }
        if (StrUtil.isNotBlank(operator.getRealName())) {
            return operator.getRealName();
        }
        return StrUtil.blankToDefault(operator.getNickname(), operatorMemberId);
    }

    private void maskMobileMemberDisplay(java.util.List<MemberCouponVerifyLogListVO> rows) {
        if (rows == null) {
            return;
        }
        for (MemberCouponVerifyLogListVO row : rows) {
            row.setMemberMobile(maskMobile(row.getMemberMobile()));
            row.setMemberNickname(maskName(row.getMemberNickname(), row.getMemberNickname()));
        }
    }

    private String maskMobile(String mobile) {
        if (StrUtil.isBlank(mobile) || mobile.length() < 7) {
            return mobile;
        }
        return mobile.substring(0, 3) + "****" + mobile.substring(mobile.length() - 4);
    }

    private String maskName(String realName, String nickname) {
        String name = StrUtil.isNotBlank(realName) ? realName : nickname;
        if (StrUtil.isBlank(name)) {
            return name;
        }
        if (name.length() <= 1) {
            return name;
        }
        return name.charAt(0) + "**";
    }

    private MarketingTenantBriefVO requireTenant(String tenantCode) {
        if (StrUtil.isBlank(tenantCode)) {
            throw new BizErrorException("店铺租户编码不能为空");
        }
        MarketingTenantBriefVO tenant = marketingTenantReadMapper.getByTenantCode(tenantCode.trim());
        if (tenant == null) {
            throw new BizErrorException("店铺不存在");
        }
        if (tenant.getStatus() != null && tenant.getStatus() == 0) {
            throw new BizErrorException("店铺已停用");
        }
        return tenant;
    }

    private void assertMemberBound(String memberId, String tenantId) {
        if (StrUtil.isBlank(memberId)) {
            throw new BizErrorException("请先登录");
        }
        var rel = memberTenantService.getByMemberAndTenant(memberId, tenantId);
        if (rel == null || !MemberTenantBindStatusEnum.BOUND.getValue().equals(rel.getBindStatus())) {
            throw new BizErrorException("请先加入该店铺");
        }
    }

    private void assertOperatorIsClerk(String operatorMemberId, String tenantId) {
        if (StrUtil.isBlank(operatorMemberId)) {
            throw new BizErrorException("请先登录");
        }
        var rel = memberTenantService.getByMemberAndTenant(operatorMemberId, tenantId);
        if (rel == null || !MemberTenantBindStatusEnum.BOUND.getValue().equals(rel.getBindStatus())) {
            throw new BizErrorException("您尚未加入该店铺");
        }
        if (!MemberTenantRoleEnum.canVerifyCoupon(rel.getMemberRole())) {
            throw new BizErrorException("无店员核销权限，请联系店长开通");
        }
    }
}
