package org.wfc.user.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wfc.common.core.domain.LoginUser;
import org.wfc.common.security.utils.SecurityUtils;
import org.wfc.user.api.IWifiApi;
import org.wfc.user.api.domain.bo.UClientBo;
import org.wfc.user.api.omada.domain.convert.OmadaConvert;
import org.wfc.user.api.omada.domain.dto.ClientRateLimitSettingDto;
import org.wfc.user.domain.UAccount;
import org.wfc.user.domain.UBill;
import org.wfc.user.domain.UBillRule;
import org.wfc.user.domain.UClient;
import org.wfc.user.domain.vo.UAccountDashboardVo;
import org.wfc.user.domain.vo.UCdrLatestHistoryVo;
import org.wfc.user.domain.vo.UCdrUserVo;
import org.wfc.user.mapper.UAccountMapper;
import org.wfc.user.mapper.UBillMapper;
import org.wfc.user.mapper.UBillRuleMapper;
import org.wfc.user.mapper.UCdrMapper;
import org.wfc.user.mapper.UClientMapper;
import org.wfc.user.service.IUAccountService;
import org.wfc.user.util.AccountUtil;
import org.wfc.user.util.BillRuleUtil;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户平台-账户表 服务实现类
 * </p>
 *
 * @author sys
 * @since 2024-12-23
 */
@Slf4j
@Service
public class UAccountServiceImpl extends ServiceImpl<UAccountMapper, UAccount> implements IUAccountService {

    @Autowired
    private UCdrMapper cdrMapper;

    @Autowired
    private IWifiApi wifiApi;

    @Autowired
    private UClientMapper clientMapper;

    @Autowired
    private UBillMapper billMapper;

    @Autowired
    private UBillRuleMapper billRuleMapper;


    // 定时任务时间间隔
    private static final int JOB_PERIOD = -30;

    @Transactional(rollbackFor = Exception.class)
    public void statAndCancelAuthUser() {
        // 定时任务查询所有未过期或刚过期（失效时间+定时任务间隔时间）的账户套餐，套餐过期/流量用完/时长用完则取消授权
        Date current = new Date();
        DateTime endTime = DateUtil.offsetSecond(current, JOB_PERIOD);
        List<UAccount> accounts = this.list(Wrappers.<UAccount>lambdaQuery()
                .and(wrapper -> wrapper.isNotNull(UAccount::getBalance).gt(UAccount::getBalance, 0).or()
                        .le(UAccount::getStartTime, endTime).gt(UAccount::getEndTime, endTime))
                .isNotNull(UAccount::getUserId));

        List<UAccount> packageAccounts = accounts.stream().filter(account -> AccountUtil.isPackageValid(account, current))
                .collect(Collectors.toList());

        List<UAccount> balanceAccounts = accounts.stream().filter(account -> !AccountUtil.isPackageValid(account, current))
                .collect(Collectors.toList());

        // 根据使用流量扣费余额
        List<Long> balanceUserIds = balanceAccounts.stream().map(UAccount::getUserId).collect(Collectors.toList());

        List<Long> cancelUserIds = new ArrayList<>();
        if (CollUtil.isNotEmpty(balanceUserIds)) {
            List<UCdrLatestHistoryVo> latestHistoryList = cdrMapper.getLatestHistory(balanceUserIds, endTime);
            List<UBillRule> billRules = billRuleMapper.selectList(Wrappers.<UBillRule>lambdaQuery().eq(UBillRule::getEnable, true));
            Optional<UBillRule> billRuleOptional = billRules.stream().findFirst();
            if (billRuleOptional.isPresent()) {
                UBillRule billRule = billRuleOptional.get();
                for (Long balanceUserId : balanceUserIds) {
                    Long currentTraffic = cdrMapper.getCurrentTraffic(balanceUserId);
                    if (ObjectUtil.isNull(currentTraffic)) {
                        continue;
                    }
                    UAccount account = this.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, balanceUserId), false);
                    account.setBalanceUsed(BillRuleUtil.calc(billRule, currentTraffic));
                    if (ObjectUtil.isNull(account.getBalance())) {
                        account.setBalance(BigDecimal.ZERO);
                    }
                    if (account.getBalance().compareTo(account.getBalanceUsed()) <= 0) {
                        if (account.getBalanceUsed().compareTo(BigDecimal.ZERO) > 0) {
                            UBill bill = new UBill();
                            bill.setUserId(account.getUserId());
                            bill.setType(2);
                            bill.setStatus(1);
                            bill.setAmount(account.getBalanceUsed());
                            billMapper.insert(bill);
                        }
                        account.setBalanceUsed(BigDecimal.ZERO);
                        account.setBalance(BigDecimal.ZERO);
                        cancelUserIds.add(account.getUserId());
                    }
                    this.updateById(account);
                }

                for (UCdrLatestHistoryVo historyVo : latestHistoryList) {
                    List<UBill> uBills = billMapper.selectList(Wrappers.<UBill>lambdaQuery().eq(UBill::getCdrHistoryId, historyVo.getId()));
                    if (CollUtil.isNotEmpty(uBills)) {
                        continue;
                    }
                    UBill bill = new UBill();
                    bill.setUserId(historyVo.getUserId());
                    bill.setCdrHistoryId(historyVo.getId());
                    bill.setStatus(1);
                    bill.setType(2);
                    bill.setAmount(BillRuleUtil.calc(billRule, historyVo.getTrafficDown() + historyVo.getTrafficUp()));
                    billMapper.insert(bill);

                    UAccount account = this.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, historyVo.getUserId()), false);
                    if (ObjectUtil.isNull(account) || (ObjectUtil.isNotNull(account.getExpiredTime())
                            && account.getExpiredTime().getTime() >= historyVo.getEndTime())) {
                        continue;
                    }
                    if (ObjectUtil.isNull(account.getBalance())) {
                        account.setBalance(BigDecimal.ZERO);
                    }
                    if (account.getBalance().compareTo(bill.getAmount()) > 0) {
                        account.setBalance(account.getBalance().subtract(bill.getAmount()));
                        account.setBalanceUsed(BigDecimal.ZERO);
                    } else {
                        account.setBalance(BigDecimal.ZERO);
                        account.setBalanceUsed(BigDecimal.ZERO);
                        cancelUserIds.add(account.getUserId());
                    }
                    this.updateById(account);
                }
            }
        }

        // 更新账户已使用流量，已使用时长
        for (UAccount account : packageAccounts) {
            if (ObjectUtil.isNull(account.getUserId())) {
                continue;
            }
            List<UCdrUserVo> statCdr = cdrMapper.getByUser(account.getUserId(), account.getStartTime().getTime(), account.getEndTime().getTime());
            statCdr.stream().findFirst().ifPresent(stat -> {
                account.setTrafficUsed(stat.getTrafficUp() + stat.getTrafficDown());
                account.setDurationUsed(stat.getDuration());
            });
        }
        this.updateBatchById(packageAccounts);

        List<Long> changeUserIds = packageAccounts.stream().filter(account -> !AccountUtil.isPackageValid(account, current) && AccountUtil.isBalanceValid(account))
                .map(UAccount::getUserId)
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(changeUserIds)) {
            List<UClient> clients = clientMapper.selectList(Wrappers.<UClient>lambdaQuery().isNotNull(UClient::getSiteId)
                    .in(UClient::getUserId, changeUserIds));
            for (UClient client : clients) {
                try {
                    wifiApi.reconnectClient(client.getSiteId(), client.getClientMac());

                    // 取消带宽限速
                    ClientRateLimitSettingDto clientRateLimitSetting = new ClientRateLimitSettingDto();
                    clientRateLimitSetting.setRateLimitEnable(false);
                    wifiApi.updateClientRateLimitSetting(client.getSiteId(), client.getClientMac(), clientRateLimitSetting);
                } catch (Exception e) {
                    log.info("reconnect error: {}", e.getMessage());
                }
            }
            ArrayList<UAccount> changeAccounts = new ArrayList<>();
            Date expiredTime = new Date();
            for (Long changeUserId : changeUserIds) {
                UAccount changeAccount = new UAccount();
                changeAccount.setId(changeUserId);
                changeAccount.setExpiredTime(expiredTime);
            }
            this.updateBatchById(changeAccounts);
        }

        // 取消授权
        List<Long> userIds = packageAccounts.stream().filter(account -> !AccountUtil.isValid(account, current))
                .map(UAccount::getUserId)
                .collect(Collectors.toList());
        userIds.addAll(cancelUserIds);

        if (CollUtil.isNotEmpty(userIds)) {
            List<UClient> clients = clientMapper.selectList(Wrappers.<UClient>lambdaQuery().isNotNull(UClient::getSiteId)
                    .in(UClient::getUserId, userIds));
            for (UClient client : clients) {
                try {
                    wifiApi.cancelAuthClient(client.getSiteId(), client.getClientMac());

                    // 取消带宽限速
                    ClientRateLimitSettingDto clientRateLimitSetting = new ClientRateLimitSettingDto();
                    clientRateLimitSetting.setRateLimitEnable(false);
                    wifiApi.updateClientRateLimitSetting(client.getSiteId(), client.getClientMac(), clientRateLimitSetting);
                } catch (Exception e) {
                    log.info("unAuth error: {}", e.getMessage());
                }
            }
        }

    }

    public void authClientAndRateLimit(UClientBo client) {
        UAccount account = this.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, client.getUserId()), false);
        if (ObjectUtil.isNull(account)) {
            return;
        }
        Date current = new Date();
        if (AccountUtil.isValid(account, current)) {
            wifiApi.authClient(client.getSiteId(), client.getClientMac());

            // 带宽限速
            ClientRateLimitSettingDto clientRateLimitSetting = OmadaConvert.INSTANCE.toClientRateLimitSettingDto(account);
            wifiApi.updateClientRateLimitSetting(client.getSiteId(), client.getClientMac(), clientRateLimitSetting);
        }

    }

    @Override
    public UAccountDashboardVo getByUser() {
        LoginUser<Object> loginUser = SecurityUtils.getLoginUser();
        UAccount account = this.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, loginUser.getUserid()), false);
        UAccountDashboardVo dashboardVo = new UAccountDashboardVo();
        if (ObjectUtil.isNull(account)) {
            dashboardVo.setId(loginUser.getUserid());
            dashboardVo.setBalance(BigDecimal.ZERO);
            dashboardVo.setTraffic(0L);
            dashboardVo.setDuration(0L);
            dashboardVo.setTrafficUsed(0L);
            dashboardVo.setDurationUsed(0L);
            dashboardVo.setClientNum(0);
            dashboardVo.setClientNumUsed(0);
            dashboardVo.setActivity(0L);
        } else {
            BeanUtils.copyProperties(account, dashboardVo);
            dashboardVo.setId(loginUser.getUserid());
            dashboardVo.setActivity(0L);
            BigDecimal balanceUsed = Optional.ofNullable(account.getBalanceUsed()).orElse(BigDecimal.ZERO);
            BigDecimal balance = Optional.ofNullable(dashboardVo.getBalance()).orElse(BigDecimal.ZERO);
            if (balance.compareTo(balanceUsed) >= 0) {
                dashboardVo.setBalance(balance.subtract(balanceUsed));

            } else {
                dashboardVo.setBalance(BigDecimal.ZERO);
            }
        }
        return dashboardVo;
    }

}
