package com.ai.da.service.impl;

import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.CreditsEventsEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.CreditsDetailMapper;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.mapper.primary.entity.CreditsDetail;
import com.ai.da.model.dto.QueryIncomeOrExpenditureDTO;
import com.ai.da.service.AccountService;
import com.ai.da.service.CreditsService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service
@Slf4j
public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, CreditsDetail> implements CreditsService {

    @Value("${redis.key.credits.pre-deduction}")
    private String creditsDeduction;

    @Resource
    private AccountService accountService;
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private RedisUtil redisUtil;

    @Override
    public Boolean buyCredits(Long accountId, Float quantity) {
        Account account = accountMapper.selectById(accountId);
        BigDecimal existingCredits = account.getCredits();
        BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity));
        BigDecimal added = existingCredits.add(newCredits);
        accountService.updateCreditsAndEndTime(account, added.toString(), null, null);
        return Boolean.TRUE;
    }

    @Override
    public String getCredits(Long accountId) {
        Account account = accountMapper.selectById(accountId);
        return Objects.isNull(account.getCredits()) ? "0" : account.getCredits().toString();
    }

    public void creditsRefund(Long accountId, Integer quantity, String orderNo) {
        Account account = accountMapper.selectById(accountId);
        BigDecimal existingCredits = account.getCredits();
        BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity));
        BigDecimal subtracted = existingCredits.subtract(newCredits);
        // 更新t_account表
        accountService.updateCreditsAndEndTime(account, subtracted.toString(), null, null);
        // 更新t_credits_details表
        // 添加积分变更记录
        insertToCreditsDetail(accountId,
                CreditsEventsEnum.REFUND.getName() + "--Stripe",
                String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
                "negative", orderNo);
    }

    /**
     * 向积分变更详细表添加记录
     *
     * @param changeEvent 导致积分变更的事件
     * @param credits     变更的积分
     * @param changeType  变更类型 ： positive->增  negative->减
     */
    @Override
    public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType, String orderNo) {
        CreditsDetail creditsDetail = new CreditsDetail();
        Account account = accountMapper.selectById(accountId);
//        BigDecimal finalCredits;
        String changeCredits;
        if ("positive".equals(changeType)) {
//            finalCredits = account.getCredits().add(new BigDecimal(credits));
            changeCredits = "+" + credits;
        } else {
//            finalCredits = account.getCredits().subtract(new BigDecimal(credits));
            changeCredits = "-" + credits;
        }
        creditsDetail.setAccountId(accountId);
        creditsDetail.setChangeEvent(changeEvent);
        creditsDetail.setChangedCredits(changeCredits);
        creditsDetail.setTaskId(orderNo);
//        creditsDetail.setCredits(finalCredits);
        creditsDetail.setCredits(account.getCredits());
        creditsDetail.setCreateTime(LocalDateTime.now());

        baseMapper.insert(creditsDetail);
    }

    @Override
    public PageBaseResponse<CreditsDetail> queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO) {
        QueryWrapper<CreditsDetail> qw = new QueryWrapper<>();
        qw.eq("account_id", UserContext.getUserHolder().getId());

        String startTime = queryPageByTimeDTO.getStartTime();
        String endTime = queryPageByTimeDTO.getEndTime();
        if (StringUtil.isNullOrEmpty(startTime)) {
            startTime = "2024-03-01 00:00:00";
        }
        if (StringUtil.isNullOrEmpty(endTime)) {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            endTime = now.format(dateTimeFormatter);
        }
        if (!Objects.isNull(queryPageByTimeDTO.getIsIncome())) {
            if (queryPageByTimeDTO.getIsIncome()) {
                qw.likeRight("changed_credits", "+");
            } else {
                qw.likeRight("changed_credits", "-");
            }
        }

        qw.between("create_time", startTime, endTime);
        qw.orderByDesc("create_time");
        Page<CreditsDetail> pageInfo = new Page<>(queryPageByTimeDTO.getPage(), queryPageByTimeDTO.getSize());
        Page<CreditsDetail> orderInfo = baseMapper.selectPage(pageInfo, qw);
        if (CollectionUtils.isEmpty(orderInfo.getRecords())) {
            return PageBaseResponse.success(new Page<>());
        }

        return PageBaseResponse.success(orderInfo);
    }

    public Boolean checkCredits(Long accountId, CreditsEventsEnum event, Integer num) {
        String credits = getCredits(accountId);
        if (new BigDecimal(credits)
                .subtract(new BigDecimal(event.getValue())
                        .multiply(new BigDecimal(num)))
                .compareTo(BigDecimal.ZERO) < 0) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }


    /*
     * 积分扣除升级 -- 预扣积分
     * 思路：
     * 1、先判断当前积分扣除待扣积分后，剩余积分够不够当前操作需要的积分
     * 2、将需要进行积分扣除操作请求的 任务id和需要扣除的积分存到redis
     * 3、执行成功后，从redis中拿出当前任务id对应需要扣除的积分，扣除积分，更新数据库积分，移除redis的记录
     * 4、执行失败，直接移除记录
     */
    /**
     * 积分预扣除
     */
    public Boolean creditsPreDeduction(CreditsEventsEnum event, Integer num) {

        Long accountId = UserContext.getUserHolder().getId();

        // 1、获取预扣除区的积分总和（转为 BigDecimal），未来需要预扣除的积分总和
        Set<String> keys = redisUtil.getKeysFromString(creditsDeduction + ":" + accountId + ":*");
        List<String> multiValue = redisUtil.getMultiValue(keys);
        BigDecimal sum = multiValue.stream()
                .map(value -> new BigDecimal(value))  // 将每个 String 转为 BigDecimal
                .reduce(BigDecimal.ZERO, BigDecimal::add);  // 累加

        // 2、加上本次操作的积分（BigDecimal 运算）
        BigDecimal currentDeduction = new BigDecimal(event.getValue()).multiply(new BigDecimal(num));
        sum = sum.add(currentDeduction);

        // 3、获取当前积分
        Account account = accountMapper.selectById(accountId);
        if (account == null) {
            log.error("账户:{} 不存在", accountId);
            throw new BusinessException("user.has.no.account", ResultEnum.PROMPT.getCode());
        }
        BigDecimal existingCredits = Optional.ofNullable(account.getCredits()).orElse(BigDecimal.ZERO);
        BigDecimal subtract = existingCredits.subtract(sum);

        // 4、判断剩余积分是否够本次操作
        if (subtract.compareTo(BigDecimal.ZERO) < 0) {
            // 4.1 不够，直接返回余额不够，充值
            return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    @Override
    public Boolean creditsPreDeduction(Integer credits) {

        Long accountId = UserContext.getUserHolder().getId();

        // 1、获取当前在积分预扣除区，未来需要预扣除的积分总和
        Set<String> keys = redisUtil.getKeysFromString(creditsDeduction + ":" + accountId + ":*");
        List<String> multiValue = redisUtil.getMultiValue(keys);
        // 1.1 预扣除区 积分总和
        int sum = multiValue.stream().mapToInt(Integer::parseInt).sum();
        // 1.2 加上本次操作需要扣除的积分
        sum += credits;

        // 2、获取当前积分
        BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
        BigDecimal subtract = existingCredits.subtract(new BigDecimal(sum));

        // 3、判断剩余积分是否够本次操作
        if (subtract.compareTo(BigDecimal.ZERO) < 0) {
            // 3.1 不够，直接返回余额不够，充值
            return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    @Override
    public void addRecordToCreditsDeduction(Long accountId, String taskId, CreditsEventsEnum creditsEventsEnum) {
        // 5、添加当前任务的预扣积分到redis 任务有效期一天，若待扣积分两天还没被移除，说明任务已经失败，待扣积分自动失效
        redisUtil.addToString(creditsDeduction + ":" + accountId + ":" + taskId, creditsEventsEnum.getValue(), CommonConstant.CREDITS_EXPIRE_TIME);
    }

    @Override
    public void addRecordToCreditsDeduction(Long accountId, String taskId, CreditsEventsEnum creditsEventsEnum, Integer num) {
        // 5、添加当前任务的预扣积分到redis 任务有效期一天，若待扣积分两天还没被移除，说明任务已经失败，待扣积分自动失效
        redisUtil.addToString(creditsDeduction + ":" + accountId + ":" + taskId, String.valueOf(Long.parseLong(creditsEventsEnum.getValue()) * num), CommonConstant.CREDITS_EXPIRE_TIME);
    }

    @Override
    public void deleteCreditsDeduction(Long accountId, String taskId) {
        // 5、添加当前任务的预扣积分到redis 任务有效期一天，若待扣积分两天还没被移除，说明任务已经失败，待扣积分自动失效
        redisUtil.removeFromString(creditsDeduction + ":" + accountId + ":" + taskId);
    }

    /**
     * 执行扣除积分，更新数据库
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean taskCreditsDeduction(Long accountId, String taskId) {
        log.info("指定任务的积分扣除 ： {}", taskId);
        String key = creditsDeduction + ":" + accountId + ":" + taskId;
        // 1、获取当前任务id对应的积分
        String value = redisUtil.getFromString(key);
        log.info("taskId为：{}，需消耗积分：{}", taskId, value);

        // 1.1 没有。返回，报错，未找到当前任务
        if (StringUtil.isNullOrEmpty(value)) {
            log.info("当前任务 {} 不存在，或当前任务已完成积分扣除", taskId);
            return Boolean.FALSE;
//            throw new BusinessException("当前任务不存在，无法扣除积分");
        } else {
            log.info("指定任务 {} 扣除积分 {}", taskId, value);
        }

        // 2、操作数据库，扣除积分
        Account account = accountMapper.selectById(accountId);
        BigDecimal existingCredits = account.getCredits();
        BigDecimal subtract = existingCredits.subtract(new BigDecimal(value));
        BigDecimal creditsUsage = null;
        if (!StringUtil.isNullOrEmpty(account.getOrganizationName())){
            creditsUsage = Objects.isNull(account.getCreditsUsage()) ? BigDecimal.ZERO : account.getCreditsUsage();
            creditsUsage = creditsUsage.add(new BigDecimal(value));
        }
        accountService.updateCreditsAndEndTime(account, subtract.toString(), null, creditsUsage);

        // 3、从redis中移除当前待扣积分
        redisUtil.removeFromString(key);
        return Boolean.TRUE;
    }

    public CreditsDetail getByAccountIdAndChangeEvent(Long accountId, String changeEvent, String changedCredits) {
        QueryWrapper<CreditsDetail> qw = new QueryWrapper<>();
        qw.eq("account_id", accountId);
        qw.eq("change_event", changeEvent);
        qw.eq("changed_credits", changedCredits);

        return baseMapper.selectOne(qw);
    }

    @Override
    public void preInsert(Long accountId, String changeEventName, String taskId, Boolean isPreInsert, String changedCredits) {
        CreditsDetail creditsDetail = new CreditsDetail();
        Account account = accountMapper.selectById(accountId);
        creditsDetail.setAccountId(accountId);
        creditsDetail.setTaskId(taskId);
        creditsDetail.setChangeEvent(changeEventName);
        creditsDetail.setChangedCredits(isPreInsert ? "-0" : "-" + changedCredits);
        creditsDetail.setCredits(account.getCredits());
        creditsDetail.setCreateTime(LocalDateTime.now());

        baseMapper.insert(creditsDetail);
    }

    @Override
    public void preInsert(Long accountId, String changeEventName, String taskId, Boolean isPreInsert, String changedCredits, Integer num) {
        CreditsDetail creditsDetail = new CreditsDetail();
        Account account = accountMapper.selectById(accountId);
        creditsDetail.setAccountId(accountId);
        creditsDetail.setTaskId(taskId);
        creditsDetail.setChangeEvent(changeEventName);
        creditsDetail.setNum(num);
        creditsDetail.setChangedCredits(isPreInsert ? "-0" : "-" + changedCredits);
        creditsDetail.setCredits(account.getCredits());
        creditsDetail.setCreateTime(LocalDateTime.now());

        baseMapper.insert(creditsDetail);
    }

    @Override
    public void updateChangedCredits(String accountId, String taskId) {
        QueryWrapper<CreditsDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("account_id", accountId)
                .eq("task_id", taskId);

        CreditsDetail creditsDetail = baseMapper.selectOne(queryWrapper);
        String changeEvent = creditsDetail.getChangeEvent();
        BigDecimal currentCredits = accountMapper.selectById(accountId).getCredits();
        String credits = "0";
        CreditsEventsEnum eventsEnum = CreditsEventsEnum.getByTaskName(changeEvent);
        if (Objects.nonNull(eventsEnum)){
            if (Objects.nonNull(creditsDetail.getNum()) && creditsDetail.getNum() != 0){
                credits = String.valueOf(Long.parseLong(eventsEnum.getValue()) * creditsDetail.getNum());
            }else {
                credits = eventsEnum.getValue();
            }
        }
        /*if (changeEvent.equals("Logo") ||
                changeEvent.equals("Pattern") ||
                changeEvent.equals("MoodBoard") ||
                changeEvent.equals("SketchBoard")) {
            credits = CreditsEventsEnum.LOGO.getValue();
        }else if (changeEvent.equals("Slogan")){
            credits =  CreditsEventsEnum.SLOGAN.getValue();
        }else if (changeEvent.equals("PoseTransformation")){
            credits =  CreditsEventsEnum.POSE_TRANSFORMATION.getValue();
        }else if (changeEvent.equals("Other")){
            credits = CreditsEventsEnum.OTHER.getValue();
        }*/

//        BigDecimal finalCredits = currentCredits.subtract(new BigDecimal(credits));
        String changeCredits = "-" + credits;
        log.info("taskId:{}，扣除的积分为：{}", taskId, changeCredits);

        UpdateWrapper<CreditsDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(CreditsDetail::getTaskId, taskId)
                .set(CreditsDetail::getChangedCredits, changeCredits)
                .set(CreditsDetail::getCredits, currentCredits);

        baseMapper.update(null, updateWrapper);
    }

    public CreditsDetail queryDetailByTaskId(String taskId) {
        QueryWrapper<CreditsDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("task_id", taskId);
        return baseMapper.selectOne(queryWrapper);
    }

    public void tokenUsage(Long accountId, BigDecimal totalTokenUsage){
        Account account = accountService.getById(accountId);
        BigDecimal changeCredits = totalTokenUsage.setScale(0, RoundingMode.CEILING);
        BigDecimal subtracted = account.getCredits().subtract(changeCredits);
        account.setCredits(subtracted);
        account.setUpdateDate(new Date());
        accountService.updateById(account);

        CreditsDetail creditsDetail = new CreditsDetail();
        creditsDetail.setAccountId(accountId);
        creditsDetail.setChangeEvent(CreditsEventsEnum.LLM_CONVERSATION.getName());
        creditsDetail.setChangedCredits("-" + changeCredits);
        creditsDetail.setCredits(subtracted);
        creditsDetail.setCreateTime(LocalDateTime.now());
        baseMapper.insert(creditsDetail);
        log.info("对话扣除积分，原始积分消耗量：{}， 取整：{}， 剩余积分：{}", totalTokenUsage, changeCredits, subtracted);
    }


}
