package com.ylx.point.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ylx.common.core.domain.model.WxLoginUser;
import com.ylx.common.exception.ServiceException;
import com.ylx.common.utils.SecurityUtils;
import com.ylx.lottery.domain.vo.LotteryCountResponseVO;
import com.ylx.massage.domain.TWxUser;
import com.ylx.massage.service.TWxUserService;
import com.ylx.point.domain.PointActivity;
import com.ylx.point.domain.PointActivityExpirePolicy;
import com.ylx.point.domain.PointUserLog;
import com.ylx.point.domain.dto.ApiAddPointsDTO;
import com.ylx.point.enums.PointActivityExpirePolicyEnum;
import com.ylx.point.enums.TaskNameEnum;
import com.ylx.point.mapper.PointUserLogMapper;
import com.ylx.point.service.IPointAccountService;
import com.ylx.point.service.IPointActivityExpirePolicyService;
import com.ylx.point.service.IPointActivityService;
import com.ylx.point.service.IPointUserLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Service
public class PointAccountServiceImpl implements IPointAccountService {

    @Value("${remote.local-live.base-url}")
    private String localLiveBaseUrl;

    // 定义接口路径常量
    private static final String POINT_SEND_PATH = "/lottery/lotteryActivity/sendGYYPoint";
    private static final int DEFAULT_TIMEOUT = 5000; // 5秒超时

    @Resource
    private IPointUserLogService pointUserLogService;
    @Resource
    private PointUserLogMapper pointUserLogMapper;
    @Resource
    private IPointActivityService pointActivityService;
    @Resource
    private IPointActivityExpirePolicyService pointActivityExpirePolicyService;
    @Resource
    private TWxUserService wxUserService;

    /**
     * 增加用户积分（无账户表版本）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addPoints(String openId, int points, String activityName, String bizOrderNo, Long activityId, Long taskId, Integer taskType, String taskName) {
        if (points <= 0) {
            return getBalance(openId);
        }

        // 1. 计算过期时间
        Date expireTime = calculateExpireTime(activityId);

        // 2. 获取当前余额 (变动前)
        Integer currentBalance = getBalance(openId);

        // 3. 写入积分流水表 (point_user_log)
        PointUserLog log = new PointUserLog();
        log.setOpenId(openId);
        log.setActivityId(activityId);
        log.setTaskId(taskId);
        log.setTaskType(taskType);
        log.setTaskName(taskName);
        log.setPoints(points);
        log.setOpType(1);        // 1-收入
        log.setBizOrderId(bizOrderNo);
        log.setSourceLogId(null);
        log.setIsExpired(0);
        log.setActivityName(activityName);
        log.setExpireTime(expireTime);
        log.setMonth(DateUtil.format(new Date(), "yyyyMM"));

        // 计算变动后余额 (快照)
        Integer newBalance = currentBalance + points;
        log.setBalanceAfter(newBalance);

        boolean saveSuccess = pointUserLogService.save(log);
        if (!saveSuccess) {
            throw new ServiceException("积分流水记录失败");
        }

        return newBalance;
    }

    /**
     * 扣减用户积分（用于积分商品下单）
     * 扣减顺序：先获取的先扣减（FIFO原则）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer deductPoints(String openId, int pointsToDeduct, String bizOrderNo, String taskName) {

        // 1. 获取当前积分余额
        Integer currentBalance = getBalance(openId);
        if (currentBalance < pointsToDeduct) {
            throw new ServiceException("积分余额不足，当前余额：" + currentBalance + "，需要扣减：" + pointsToDeduct);
        }

        // 2. 查询用户所有未过期的收入记录，按获取时间升序排序（FIFO原则）
        LambdaQueryWrapper<PointUserLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PointUserLog::getOpenId, openId)
                .eq(PointUserLog::getOpType, 1)  // 1-收入
                .eq(PointUserLog::getIsExpired, 0)  // 0-未过期
                .and(wrapper -> wrapper.isNull(PointUserLog::getExpireTime)
                        .or()
                        .gt(PointUserLog::getExpireTime, new Date()))  // 未过期或无过期时间
                .orderByAsc(PointUserLog::getCreateTime);  // 按获取时间升序

        List<PointUserLog> availablePoints = pointUserLogService.list(queryWrapper);

        // 3. 按FIFO原则扣减积分
        int remainingToDeduct = pointsToDeduct;
        for (PointUserLog sourceLog : availablePoints) {
            if (remainingToDeduct <= 0) {
                break;
            }

            // 计算这条记录可以扣减的积分
            // 需要查询这条记录已经被扣减了多少
            LambdaQueryWrapper<PointUserLog> deductQuery = new LambdaQueryWrapper<>();
            deductQuery.eq(PointUserLog::getOpenId, openId)
                    .eq(PointUserLog::getOpType, 2)  // 2-支出
                    .eq(PointUserLog::getSourceLogId, sourceLog.getId());
            List<PointUserLog> deductLogs = pointUserLogService.list(deductQuery);

            int alreadyDeducted = deductLogs.stream()
                    .mapToInt(PointUserLog::getPoints)
                    .map(Math::abs)
                    .sum();

            int availableInThisRecord = sourceLog.getPoints() - alreadyDeducted;

            if (availableInThisRecord <= 0) {
                continue;  // 这条记录已经完全扣减，跳过
            }

            // 计算本次需要从这条记录扣减的积分
            int deductFromThisRecord = Math.min(remainingToDeduct, availableInThisRecord);

            // 4. 创建扣减记录
            PointUserLog deductLog = new PointUserLog();
            deductLog.setOpenId(openId);
            // 扣减积分
            deductLog.setPoints(deductFromThisRecord);
            deductLog.setOpType(2);  // 2-支出
            deductLog.setBizOrderId(bizOrderNo);
            deductLog.setSourceLogId(sourceLog.getId());  // 关联到收入记录
            // 任务类型: 1-普通任务
            deductLog.setTaskType(1);
            // 任务名称
            deductLog.setTaskName(taskName);
            deductLog.setMonth(DateUtil.format(new Date(), "yyyyMM"));

            // 计算扣减后的余额
            Integer newBalance = currentBalance - deductFromThisRecord;
            deductLog.setBalanceAfter(newBalance);

            boolean saveSuccess = pointUserLogService.save(deductLog);
            if (!saveSuccess) {
                throw new ServiceException("积分扣减记录失败");
            }

            // 更新剩余需要扣减的积分
            remainingToDeduct -= deductFromThisRecord;
            currentBalance = newBalance;

            // 5. 检查源记录是否完全扣减，如果是则标记为已过期
            if (availableInThisRecord == deductFromThisRecord) {
                sourceLog.setIsExpired(1);  // 1-已完全核销
                pointUserLogService.updateById(sourceLog);
            }
        }
        return currentBalance;
    }

    /**
     * 退款积分（用于积分商品退款）
     * 退回的积分没有过期时间
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer refundPoints(String openId, int pointsToRefund, String bizOrderNo, String activityName) {
        if (pointsToRefund <= 0) {
            return getBalance(openId);
        }

        // 1. 获取当前余额 (变动前)
        Integer currentBalance = getBalance(openId);

        // 2. 写入积分流水表 (point_user_log)
        PointUserLog log = new PointUserLog();
        log.setOpenId(openId);
        log.setPoints(pointsToRefund);
        log.setOpType(1);  // 1-收入
        log.setBizOrderId(bizOrderNo);
        log.setSourceLogId(null);
        log.setIsExpired(0);  // 0-未过期
        log.setActivityName(TaskNameEnum.GOODS_REFUND.getInfo());
        log.setExpireTime(null);  // 退款积分没有过期时间
        log.setMonth(DateUtil.format(new Date(), "yyyyMM"));

        // 计算变动后余额 (快照)
        Integer newBalance = currentBalance + pointsToRefund;
        log.setBalanceAfter(newBalance);

        boolean saveSuccess = pointUserLogService.save(log);
        if (!saveSuccess) {
            throw new RuntimeException("积分退款记录失败");
        }

        return newBalance;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void apiAddPoints(ApiAddPointsDTO dto) {

        // 根据userId查询用户信息
        TWxUser user = this.wxUserService.getById(dto.getUserId());
        if (ObjUtil.isEmpty(user)) {
            log.error("用户信息不存在, userId: {}", dto.getUserId());
            throw new ServiceException("用户信息不存在");
        }

        String openId = user.getcOpenid();

        // 1. 获取当前余额 (变动前)
        Integer currentBalance = getBalance(openId);

        // 2. 写入积分流水表 (point_user_log)
        PointUserLog log = new PointUserLog();
        log.setOpenId(openId);
        log.setTaskType(3);
        log.setPoints(dto.getPoints());
        log.setOpType(1);        // 1-收入
        log.setIsExpired(0);
        log.setActivityName(TaskNameEnum.LOCAL_LIVE_LOTTERY.getInfo());
        log.setMonth(DateUtil.format(new Date(), "yyyyMM"));

        // 计算变动后余额 (快照)
        Integer newBalance = currentBalance + dto.getPoints();
        log.setBalanceAfter(newBalance);

        boolean saveSuccess = pointUserLogService.save(log);
        if (!saveSuccess) {
            throw new ServiceException("积分流水记录失败");
        }

    }

    @Override
    public void draw() {

        WxLoginUser loginUser = SecurityUtils.getWxLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            throw new ServiceException("用户未登录或登录已过期");
        }

        String localLiveUserId = loginUser.getLocalLiveUserId();
        Integer isBind = loginUser.getIsBind();
        String openId = loginUser.getCOpenid();

        if (ObjectUtil.isNotNull(isBind) && (ObjectUtil.equals(isBind, 1) && ObjectUtil.isNotNull(localLiveUserId))) {
            Integer points = pointSend(localLiveUserId);
            // 1. 获取当前余额 (变动前)
            Integer currentBalance = getBalance(openId);

            // 2. 写入积分流水表 (point_user_log)
            PointUserLog log = new PointUserLog();
            log.setOpenId(openId);
            log.setTaskType(3);
            log.setPoints(points);
            log.setOpType(1);        // 1-收入
            log.setIsExpired(0);
            log.setActivityName(TaskNameEnum.LOCAL_LIVE_LOTTERY.getInfo());
            log.setMonth(DateUtil.format(new Date(), "yyyyMM"));

            // 计算变动后余额 (快照)
            Integer newBalance = currentBalance + points;
            log.setBalanceAfter(newBalance);

            boolean saveSuccess = pointUserLogService.save(log);
            if (!saveSuccess) {
                throw new ServiceException("积分流水记录失败");
            }
        } else {
            throw new ServiceException("用户未绑定本地生活账号");
        }
    }

    /**
     * 获取用户当前可用积分余额
     * 逻辑：未过期收入总和 + 支出总和（支出为负数，直接相加）
     *
     * @param openId 用户ID
     * @return Integer 用户当前可用积分余额
     */
    private Integer getBalance(String openId) {
        Integer balance = pointUserLogMapper.selectAvailableBalance(openId);
        return balance != null ? balance : 0;
    }


    /**
     * 计算过期时间
     * 优化点：
     * 1. 增加缓存注解，减少数据库查询
     * 2. 统一使用 LocalDateTime 和 DateUtil，避免 Calendar 的繁琐与易错
     * 3. 增加字段判空，防止 NPE
     */
    private Date calculateExpireTime(Long activityId) {
        if (activityId == null) {
            return null;
        }

        // 1. 获取活动信息 (建议 Service 层也配置缓存)
        PointActivity activity = pointActivityService.getById(activityId);
        if (activity == null || activity.getExpirePolicyId() == null) {
            return null;
        }

        // 2. 获取过期策略 (建议 Service 层也配置缓存)
        PointActivityExpirePolicy policy = pointActivityExpirePolicyService.getById(activity.getExpirePolicyId());
        if (policy == null || policy.getExpirePolicy() == null) {
            return null;
        }

        LocalDateTime now = LocalDateTime.now();
        Integer expirePolicy = policy.getExpirePolicy();

        // 3. 永久有效
        if (PointActivityExpirePolicyEnum.PERMANENT_VALID.getCode().equals(expirePolicy)) {
            return null;
        }

        // 4. N天有效 (增加判空，默认为0)
        if (PointActivityExpirePolicyEnum.TRANSACTION_EXPIRE.getCode().equals(expirePolicy)) {
            Integer days = Optional.ofNullable(policy.getExpireDays()).orElse(0);
            return DateUtil.offsetDay(new Date(), days);
        }

        // 5. 固定年份 (次年/第N年) 1月1日过期
        // 逻辑修正：通常"固定年份"指的是截止到当年的最后一天，或者次年的第一天。
        // 这里假设逻辑是：当前年份 + 偏移量，当年的1月1日 (或者根据业务调整为12月31日)
        if (PointActivityExpirePolicyEnum.YEARLY_EXPIRE.getCode().equals(expirePolicy)) {
            Integer yearOffset = Optional.ofNullable(policy.getExpireYear()).orElse(0);

            // 计算目标年份的 1月1日 00:00:00 (或者根据需求调整为 12月31日 23:59:59)
            LocalDateTime expireTime = now
                    .plusYears(yearOffset)
                    .withMonth(1)
                    .withDayOfMonth(1)
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0);

            return DateUtil.date(expireTime);
        }

        return null;
    }

    private Integer pointSend(String userId) {
        try {
            // 1. 构建请求参数
            String url = localLiveBaseUrl + POINT_SEND_PATH;
            Map<String, Object> params = Collections.singletonMap("userId", userId);

            // 2. 发送请求
            String resultJson = HttpUtil.get(url, params, DEFAULT_TIMEOUT);

            // 3. 校验结果
            if (StrUtil.isEmpty(resultJson)) {
                throw new RuntimeException("广誉源领取积分接口失败：接口返回空结果");
            }

            // 4. 解析结果
            LotteryCountResponseVO<Integer> response = JSON.parseObject(
                    resultJson,
                    new TypeReference<LotteryCountResponseVO<Integer>>() {
                    }
            );

            // 5. 处理业务状态
            if (ObjUtil.isNull(response) || !response.isSuccess()) {
                String msg = ObjUtil.isNotNull(response) ? response.getMessage() : "未知错误";
                Integer code = ObjUtil.isNotNull(response) ? response.getCode() : -1;

                log.warn("广誉源领取积分接口业务失败: code={}, msg={}", code, msg);
                throw new ServiceException("远程接口返回失败: " + msg);
            }

            // 6. 获取数据列表
            Integer points = response.getResult();

            log.info("成功获取广誉源领取积分，数量: {}", points);

            return points;

        } catch (ServiceException e) {
            // 透传业务异常，不要重复包装
            throw e;
        } catch (Exception e) {
            log.error("广誉源领取积分接口发生系统异常", e);
            // 优化点：只抛出消息，不要直接把整个 Exception 对象 toString() 拼接到字符串里
            throw new ServiceException("广誉源领取积分接口异常: " + e.getMessage());
        }
    }

}
