package com.jeesharp.business.modules.member.application;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.member.application.support.MemberPointsCalculator;
import com.jeesharp.business.modules.member.dto.MemberPointsDeductDTO;
import com.jeesharp.business.modules.member.dto.MemberPointsGrantDTO;
import com.jeesharp.business.modules.member.dto.MemberPointsGrantResultDTO;
import com.jeesharp.business.modules.member.dto.MemberPointsRevokeDTO;
import com.jeesharp.business.modules.member.dto.MemberPointsTrialDTO;
import com.jeesharp.business.modules.member.dto.MemberPointsTrialResultDTO;
import com.jeesharp.business.modules.member.entity.Member;
import com.jeesharp.business.modules.member.entity.MemberPointsGrantRecord;
import com.jeesharp.business.modules.member.entity.MemberPointsRule;
import com.jeesharp.business.modules.member.entity.MemberPointsRuleLevel;
import com.jeesharp.business.modules.member.enums.MemberPointsBizTypeEnum;
import com.jeesharp.business.modules.member.enums.MemberPointsSceneEnum;
import com.jeesharp.business.modules.member.service.MemberLevelService;
import com.jeesharp.business.modules.member.service.MemberPointsGrantRecordService;
import com.jeesharp.business.modules.member.service.MemberPointsRuleService;
import com.jeesharp.business.modules.member.service.MemberService;
import com.jeesharp.framework.utils.base.ExceptionUtil;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * 会员积分应用服务：按场景规则试算、幂等发放、扣减与冲正编排。
 *
 * @author jeesharp
 * @since 2026-06-23
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class MemberPointsApplicationService {

    private final MemberService memberService;
    private final MemberLevelService memberLevelService;
    private final MemberPointsRuleService memberPointsRuleService;
    private final MemberPointsGrantRecordService memberPointsGrantRecordService;
    private final MemberPointsCalculator memberPointsCalculator;

    public MemberPointsTrialResultDTO trial(MemberPointsTrialDTO dto) {
        validateScene(dto.getSceneCode());
        Member member = requireMember(dto.getMemberId());
        MemberPointsRule rule = requireEnabledRule(dto.getSceneCode());
        MemberPointsRuleLevel levelDetail = resolveLevelDetail(member, rule);
        int points = memberPointsCalculator.calculate(member, rule, levelDetail,
                dto.getContext() != null ? dto.getContext() : Map.of());

        MemberPointsTrialResultDTO result = new MemberPointsTrialResultDTO();
        result.setPoints(points);
        result.setRuleId(rule.getId());
        result.setRuleName(rule.getRuleName());
        return result;
    }

    @Transactional(readOnly = false)
    public MemberPointsGrantResultDTO grant(MemberPointsGrantDTO dto) {
        validateGrantDto(dto);
        MemberPointsGrantRecord existing = memberPointsGrantRecordService.findActive(
                dto.getMemberId(), dto.getSceneCode(), dto.getBizId());
        if (existing != null) {
            return buildIdempotentResult(existing);
        }

        Member member = requireMember(dto.getMemberId());
        MemberPointsRule rule = requireEnabledRule(dto.getSceneCode());
        assertWithinLimits(dto.getMemberId(), dto.getSceneCode(), rule);

        MemberPointsRuleLevel levelDetail = resolveLevelDetail(member, rule);
        Map<String, Object> context = dto.getContext() != null ? dto.getContext() : Map.of();
        int points = resolvePoints(dto.getOverridePoints(), member, rule, levelDetail, context);
        if (points <= 0) {
            return buildZeroResult(rule);
        }

        String bizType = resolveBizType(dto.getSceneCode());
        String bizDesc = StrUtil.isNotBlank(dto.getBizDesc())
                ? dto.getBizDesc() : MemberPointsSceneEnum.getDescByCode(dto.getSceneCode());
        LocalDateTime expireDate = resolveExpireDate(rule);
        String operateSource = StrUtil.isNotBlank(dto.getOperateSource()) ? dto.getOperateSource() : "SYSTEM";

        MemberPointsGrantRecord record = buildGrantRecord(dto, rule, points, bizDesc);
        if (!memberPointsGrantRecordService.trySaveRecord(record)) {
            MemberPointsGrantRecord conflict = memberPointsGrantRecordService.findActive(
                    dto.getMemberId(), dto.getSceneCode(), dto.getBizId());
            if (conflict != null) {
                return buildIdempotentResult(conflict);
            }
            ExceptionUtil.error("积分发放冲突，请重试");
        }

        String pointsLogId = memberService.addPointsWithRule(
                dto.getMemberId(), points, bizType, dto.getBizId(), bizDesc,
                rule.getId(), rule.getRuleName(), expireDate, operateSource);
        memberPointsGrantRecordService.updatePointsLogId(record.getId(), pointsLogId);

        return buildGrantedResult(record, pointsLogId, points);
    }

    @Transactional(readOnly = false)
    public MemberPointsGrantResultDTO deduct(MemberPointsDeductDTO dto) {
        validateDeductDto(dto);
        MemberPointsGrantRecord existing = memberPointsGrantRecordService.findActive(
                dto.getMemberId(), dto.getSceneCode(), dto.getBizId());
        if (existing != null) {
            return buildIdempotentResult(existing);
        }

        Member requireMember = requireMember(dto.getMemberId());
        MemberPointsRule rule = requireEnabledRule(dto.getSceneCode());
        int points = dto.getPoints();
        String bizType = resolveBizType(dto.getSceneCode());
        String bizDesc = StrUtil.isNotBlank(dto.getBizDesc())
                ? dto.getBizDesc() : MemberPointsSceneEnum.getDescByCode(dto.getSceneCode());
        String operateSource = StrUtil.isNotBlank(dto.getOperateSource()) ? dto.getOperateSource() : "SYSTEM";

        MemberPointsGrantRecord record = new MemberPointsGrantRecord();
        record.setMemberId(dto.getMemberId());
        record.setSceneCode(dto.getSceneCode());
        record.setBizId(dto.getBizId());
        record.setBizNo(dto.getBizNo());
        record.setBizDesc(bizDesc);
        record.setRuleId(rule.getId());
        record.setRuleName(rule.getRuleName());
        record.setPoints(points);
        record.setRevoked(0);

        if (!memberPointsGrantRecordService.trySaveRecord(record)) {
            MemberPointsGrantRecord conflict = memberPointsGrantRecordService.findActive(
                    dto.getMemberId(), dto.getSceneCode(), dto.getBizId());
            if (conflict != null) {
                return buildIdempotentResult(conflict);
            }
            ExceptionUtil.error("积分扣减冲突，请重试");
        }

        String pointsLogId = memberService.deductPointsWithRule(
                requireMember.getId(), points, bizType, dto.getBizId(), bizDesc,
                rule.getId(), rule.getRuleName(), operateSource);
        memberPointsGrantRecordService.updatePointsLogId(record.getId(), pointsLogId);
        return buildGrantedResult(record, pointsLogId, points);
    }

    @Transactional(readOnly = false)
    public void revoke(MemberPointsRevokeDTO dto) {
        if (dto == null || StrUtil.hasBlank(dto.getMemberId(), dto.getSceneCode(), dto.getBizId())) {
            ExceptionUtil.error("冲正参数不完整");
        }
        MemberPointsGrantRecord existing = memberPointsGrantRecordService.findActive(
                dto.getMemberId(), dto.getSceneCode(), dto.getBizId());
        if (existing == null || existing.getPoints() == null || existing.getPoints() <= 0) {
            return;
        }
        String bizType = StrUtil.isNotBlank(dto.getRevokeBizType())
                ? dto.getRevokeBizType() : MemberPointsBizTypeEnum.ORDER_CANCEL.getCode();
        String bizDesc = StrUtil.isNotBlank(dto.getBizDesc()) ? dto.getBizDesc() : "积分冲正";
        String operateSource = StrUtil.isNotBlank(dto.getOperateSource()) ? dto.getOperateSource() : "SYSTEM";
        memberService.deductPointsWithRule(
                dto.getMemberId(), existing.getPoints(), bizType, dto.getBizId(), bizDesc,
                existing.getRuleId(), existing.getRuleName(), operateSource);
        memberPointsGrantRecordService.markRevoked(existing.getId());
    }

    /**
     * 新会员注册后发放注册积分（幂等）。
     */
    @Transactional(readOnly = false)
    public MemberPointsGrantResultDTO grantRegisterPoints(String memberId) {
        return grant(MemberPointsGrantDTO.builder()
                .memberId(memberId)
                .sceneCode(MemberPointsSceneEnum.REGISTER.getCode())
                .bizId(memberId)
                .bizDesc("新会员注册赠送")
                .operateSource("SYSTEM")
                .build());
    }

    /**
     * 管理端人工增加积分。
     */
    @Transactional(readOnly = false)
    public MemberPointsGrantResultDTO grantManualAdjust(String memberId, int points, String bizDesc) {
        return grant(MemberPointsGrantDTO.builder()
                .memberId(memberId)
                .sceneCode(MemberPointsSceneEnum.MANUAL.getCode())
                .bizId(IdUtil.getSnowflakeNextIdStr())
                .bizDesc(StrUtil.isNotBlank(bizDesc) ? bizDesc : "人工增加积分")
                .operateSource("ADMIN")
                .overridePoints(points)
                .build());
    }

    /**
     * 管理端人工扣减积分。
     */
    @Transactional(readOnly = false)
    public MemberPointsGrantResultDTO deductManualAdjust(String memberId, int points, String bizDesc) {
        return deduct(MemberPointsDeductDTO.builder()
                .memberId(memberId)
                .sceneCode(MemberPointsSceneEnum.MANUAL.getCode())
                .bizId(IdUtil.getSnowflakeNextIdStr())
                .points(points)
                .bizDesc(StrUtil.isNotBlank(bizDesc) ? bizDesc : "人工扣减积分")
                .operateSource("ADMIN")
                .build());
    }

    private void validateGrantDto(MemberPointsGrantDTO dto) {
        if (dto == null || StrUtil.hasBlank(dto.getMemberId(), dto.getSceneCode(), dto.getBizId())) {
            ExceptionUtil.error("积分发放参数不完整");
        }
        validateScene(dto.getSceneCode());
    }

    private void validateDeductDto(MemberPointsDeductDTO dto) {
        if (dto == null || StrUtil.hasBlank(dto.getMemberId(), dto.getSceneCode(), dto.getBizId())) {
            ExceptionUtil.error("积分扣减参数不完整");
        }
        if (dto.getPoints() == null || dto.getPoints() <= 0) {
            ExceptionUtil.error("扣减积分必须大于0");
        }
        validateScene(dto.getSceneCode());
    }

    private void validateScene(String sceneCode) {
        if (MemberPointsSceneEnum.fromCode(sceneCode) == null) {
            ExceptionUtil.error("不支持的积分场景：" + sceneCode);
        }
    }

    private Member requireMember(String memberId) {
        Member member = memberService.get(memberId);
        if (member == null) {
            ExceptionUtil.error("会员不存在");
        }
        return member;
    }

    private MemberPointsRule requireEnabledRule(String sceneCode) {
        MemberPointsRule rule = memberPointsRuleService.getEnabledBySceneCode(sceneCode);
        if (rule == null) {
            ExceptionUtil.error("积分场景未配置或已禁用：" + sceneCode);
        }
        return rule;
    }

    private void assertWithinLimits(String memberId, String sceneCode, MemberPointsRule rule) {
        if (rule.getDailyLimit() != null && rule.getDailyLimit() > 0) {
            long todayCount = memberPointsGrantRecordService.countTodayByMemberAndScene(
                    memberId, sceneCode, LocalDate.now());
            if (todayCount >= rule.getDailyLimit()) {
                ExceptionUtil.error("今日该场景积分已达上限");
            }
        }
        if (rule.getTotalLimit() != null && rule.getTotalLimit() > 0) {
            long totalCount = memberPointsGrantRecordService.countTotalByMemberAndScene(memberId, sceneCode);
            if (totalCount >= rule.getTotalLimit()) {
                ExceptionUtil.error("该场景积分累计已达上限");
            }
        }
    }

    private int resolvePoints(Integer overridePoints, Member member, MemberPointsRule rule,
                              MemberPointsRuleLevel levelDetail, Map<String, Object> context) {
        if (overridePoints != null) {
            return Math.max(0, overridePoints);
        }
        return memberPointsCalculator.calculate(member, rule, levelDetail, context);
    }

    private MemberPointsGrantRecord buildGrantRecord(MemberPointsGrantDTO dto, MemberPointsRule rule,
                                                     int points, String bizDesc) {
        MemberPointsGrantRecord record = new MemberPointsGrantRecord();
        record.setMemberId(dto.getMemberId());
        record.setSceneCode(dto.getSceneCode());
        record.setBizId(dto.getBizId());
        record.setBizNo(dto.getBizNo());
        record.setBizDesc(bizDesc);
        record.setRuleId(rule.getId());
        record.setRuleName(rule.getRuleName());
        record.setPoints(points);
        record.setRevoked(0);
        return record;
    }

    private MemberPointsGrantResultDTO buildIdempotentResult(MemberPointsGrantRecord existing) {
        MemberPointsGrantResultDTO result = new MemberPointsGrantResultDTO();
        result.setGranted(existing.getPoints() != null && existing.getPoints() > 0);
        result.setIdempotentHit(true);
        result.setPoints(existing.getPoints() != null ? existing.getPoints() : 0);
        result.setRuleId(existing.getRuleId());
        result.setRuleName(existing.getRuleName());
        result.setGrantRecordId(existing.getId());
        result.setPointsLogId(existing.getPointsLogId());
        return result;
    }

    private MemberPointsGrantResultDTO buildZeroResult(MemberPointsRule rule) {
        MemberPointsGrantResultDTO result = new MemberPointsGrantResultDTO();
        result.setGranted(false);
        result.setIdempotentHit(false);
        result.setPoints(0);
        result.setRuleId(rule.getId());
        result.setRuleName(rule.getRuleName());
        return result;
    }

    private MemberPointsGrantResultDTO buildGrantedResult(MemberPointsGrantRecord record,
                                                          String pointsLogId, int points) {
        MemberPointsGrantResultDTO result = new MemberPointsGrantResultDTO();
        result.setGranted(true);
        result.setIdempotentHit(false);
        result.setPoints(points);
        result.setRuleId(record.getRuleId());
        result.setRuleName(record.getRuleName());
        result.setGrantRecordId(record.getId());
        result.setPointsLogId(pointsLogId);
        return result;
    }

    private MemberPointsRuleLevel resolveLevelDetail(Member member, MemberPointsRule rule) {
        String levelId = member.getLevelId();
        if (StrUtil.isBlank(levelId) && StrUtil.isNotBlank(member.getLevelCode())) {
            var level = memberLevelService.getByLevelCode(member.getLevelCode());
            if (level != null) {
                levelId = level.getId();
            }
        }
        return memberPointsRuleService.resolveLevelDetail(rule.getId(), levelId);
    }

    private LocalDateTime resolveExpireDate(MemberPointsRule rule) {
        if (rule.getValidDays() == null || rule.getValidDays() <= 0) {
            return null;
        }
        return LocalDateTime.now().plusDays(rule.getValidDays());
    }

    private String resolveBizType(String sceneCode) {
        MemberPointsSceneEnum scene = MemberPointsSceneEnum.fromCode(sceneCode);
        if (scene == null) {
            return sceneCode;
        }
        return switch (scene) {
            case REGISTER -> MemberPointsBizTypeEnum.REGISTER.getCode();
            case SIGN_IN -> MemberPointsBizTypeEnum.SIGN_IN.getCode();
            case ORDER_CONSUME -> MemberPointsBizTypeEnum.CONSUME.getCode();
            case ORDER_CANCEL -> MemberPointsBizTypeEnum.ORDER_CANCEL.getCode();
            case BIRTHDAY -> MemberPointsBizTypeEnum.BIRTHDAY.getCode();
            case UPGRADE -> MemberPointsBizTypeEnum.UPGRADE.getCode();
            case INVITE -> MemberPointsBizTypeEnum.INVITE.getCode();
            case ACTIVITY -> MemberPointsBizTypeEnum.ACTIVITY.getCode();
            case MANUAL -> MemberPointsBizTypeEnum.MANUAL.getCode();
            case COUPON_EXCHANGE -> MemberPointsBizTypeEnum.COUPON_EXCHANGE.getCode();
        };
    }
}
