package com.ai.da.service.impl;

import com.ai.da.common.config.exception.BusinessException;
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.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.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Set;

@Service
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 void initCredits() {
        accountService.updateCredits(UserContext.getUserHolder().getId(), CreditsEventsEnum.INIT.getValue());
    }

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

    @Override
    public void creditsIncrease(Long accountId, String creditsEvent) {
        CreditsEventsEnum event = null;

        switch (creditsEvent) {
            case "Daily Check-In":
                event = CreditsEventsEnum.DAILY_CHECKIN;
                break;
            case "Social Media Sharing":
                event = CreditsEventsEnum.SOCIAL_MEDIA_SHARING;
                break;
            case "Other":
                event = CreditsEventsEnum.OTHER;
                break;
            case "Super Resolution":
                event = CreditsEventsEnum.SUPER_RESOLUTION;
                break;
            default:
                throw new BusinessException("UNKNOWN TYPE");
        }
        BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
        BigDecimal add = new BigDecimal(event.getValue()).add(existingCredits);
        accountService.updateCredits(accountId, add.toString());
    }

    @Override
    public void creditsDecrease(Long accountId, String creditsEvent) {
        CreditsEventsEnum event;

        switch (creditsEvent) {
            case "Super Resolution":
                event = CreditsEventsEnum.SUPER_RESOLUTION;
                break;
            case "Other":
                event = CreditsEventsEnum.OTHER;
                break;
            default:
                log.error("UNKNOWN TYPE");
                throw new BusinessException("UNKNOWN TYPE");
        }
        BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
        BigDecimal subtract = existingCredits.subtract(new BigDecimal(event.getValue()));
        accountService.updateCredits(accountId, subtract.toString());
    }

    @Override
    public String getCredits(Long accountId) {
        Account account = accountMapper.selectById(accountId);
        return account.getCredits().toString();
    }

    public void creditsRefund(Long accountId, Integer quantity) {
        BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
        BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity));
        BigDecimal subtracted = existingCredits.subtract(newCredits);
        accountService.updateCredits(accountId, subtracted.toString());
    }

    /**
     * 向积分变更详细表添加记录
     *
     * @param changeEvent 导致积分变更的事件
     * @param credits     变更的积分
     * @param changeType  变更类型 ： positive->增  negative->减
     */
    @Override
    public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType) {
        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.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、获取当前需要预扣除的积分
        Set<String> keys = redisUtil.getKeysFromString(creditsDeduction + ":" + accountId + ":*");
        List<String> multiValue = redisUtil.getMultiValue(keys);
        int sum = multiValue.stream().mapToInt(Integer::parseInt).sum();
        sum += Integer.parseInt(event.getValue()) * num;

        // 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
    @Transactional(rollbackFor = Exception.class)
    public void taskCreditsDeduction(Long accountId, String taskId){
        String key = creditsDeduction + ":" + accountId + ":" + taskId;
        // 1、获取当前任务id对应的积分
        String value = redisUtil.getFromString(key);

        // 1.1 没有。返回，报错，未找到当前任务
        if (StringUtil.isNullOrEmpty(value)){
            throw new BusinessException("当前任务不存在，无法扣除积分");
        }

        // 2、操作数据库，扣除积分
        BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
        BigDecimal subtract = existingCredits.subtract(new BigDecimal(value));
        accountService.updateCredits(accountId, subtract.toString());

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

}
