package cn.iocoder.yudao.module.energy.service.account;

import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.energy.controller.admin.account.vo.EnergyAccountFlowPageReqVO;
import cn.iocoder.yudao.module.energy.controller.admin.account.vo.EnergyAccountPageReqVO;
import cn.iocoder.yudao.module.energy.controller.admin.account.vo.EnergyAccountSummaryVO;
import cn.iocoder.yudao.module.energy.dal.dataobject.account.EnergyAccountDO;
import cn.iocoder.yudao.module.energy.dal.dataobject.account.EnergyAccountFlowDO;
import cn.iocoder.yudao.module.energy.dal.dataobject.account.EnergyProjectAccountDO;
import cn.iocoder.yudao.module.energy.dal.mysql.account.EnergyAccountFlowMapper;
import cn.iocoder.yudao.module.energy.dal.mysql.account.EnergyAccountMapper;
import cn.iocoder.yudao.module.energy.dal.mysql.account.EnergyProjectAccountMapper;
import cn.iocoder.yudao.module.energy.enums.AccountStatusEnum;
import cn.iocoder.yudao.module.energy.enums.BizTypeEnum;
import cn.iocoder.yudao.module.energy.enums.FlowTypeEnum;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.energy.enums.ErrorCodeConstants.*;

@Service
@Validated
@Slf4j
public class EnergyAccountServiceImpl implements EnergyAccountService {

    @Resource
    private EnergyAccountMapper accountMapper;
    @Resource
    private EnergyProjectAccountMapper projectAccountMapper;
    @Resource
    private EnergyAccountFlowMapper accountFlowMapper;

    @Override
    public PageResult<EnergyAccountDO> getAccountPage(EnergyAccountPageReqVO reqVO) {
        return accountMapper.selectPage(reqVO);
    }

    @Override
    public EnergyAccountDO getAccount(Long id) {
        return accountMapper.selectById(id);
    }

    @Override
    public EnergyAccountDO getOrCreateByCustomerId(Long customerId) {
        EnergyAccountDO account = accountMapper.selectByCustomerId(customerId);
        if (account != null) {
            return account;
        }
        // Auto-create
        account = EnergyAccountDO.builder()
                .customerId(customerId)
                .balance(BigDecimal.ZERO)
                .initBalance(BigDecimal.ZERO)
                .accumulatedRecharge(BigDecimal.ZERO)
                .accumulatedHydrogen(BigDecimal.ZERO)
                .accumulatedElectric(BigDecimal.ZERO)
                .accumulatedConsume(BigDecimal.ZERO)
                .accountStatus(AccountStatusEnum.NORMAL.getStatus())
                .version(0)
                .build();
        accountMapper.insert(account);
        return account;
    }

    @Override
    public EnergyProjectAccountDO getOrCreateByContractId(Long accountId, Long contractId, String projectName) {
        EnergyProjectAccountDO projectAccount = projectAccountMapper.selectByContractId(contractId);
        if (projectAccount != null) {
            return projectAccount;
        }
        projectAccount = EnergyProjectAccountDO.builder()
                .accountId(accountId)
                .contractId(contractId)
                .projectName(projectName)
                .projectBalance(BigDecimal.ZERO)
                .projectRemitAmount(BigDecimal.ZERO)
                .projectHydrogenAmount(BigDecimal.ZERO)
                .projectElectricAmount(BigDecimal.ZERO)
                .projectConsumeAmount(BigDecimal.ZERO)
                .accountStatus(AccountStatusEnum.NORMAL.getStatus())
                .version(0)
                .build();
        projectAccountMapper.insert(projectAccount);
        return projectAccount;
    }

    @Override
    public List<EnergyProjectAccountDO> getProjectAccountsByAccountId(Long accountId) {
        return projectAccountMapper.selectListByAccountId(accountId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deduct(Long customerId, Long contractId, BigDecimal amount, Long bizId, String bizCode, String remark) {
        // 1. getOrCreate 总账户
        EnergyAccountDO account = getOrCreateByCustomerId(customerId);
        BigDecimal balanceBefore = account.getBalance();
        BigDecimal balanceAfter = balanceBefore.subtract(amount).setScale(2, RoundingMode.HALF_UP);

        // 2. 乐观锁更新总账户余额（重试3次）
        boolean updated = updateAccountWithRetry(account, balanceAfter, amount, true);
        if (!updated) {
            throw exception(ENERGY_ACCOUNT_VERSION_CONFLICT);
        }
        // Re-read for correct balanceBefore after retry
        if (!balanceBefore.equals(account.getBalance())) {
            balanceBefore = account.getBalance();
            balanceAfter = balanceBefore.subtract(amount).setScale(2, RoundingMode.HALF_UP);
        }

        // 3. 更新项目账户
        BigDecimal projectBalanceBefore = null;
        BigDecimal projectBalanceAfter = null;
        Long projectAccountId = null;
        if (contractId != null) {
            EnergyProjectAccountDO projectAccount = getOrCreateByContractId(account.getId(), contractId, null);
            projectBalanceBefore = projectAccount.getProjectBalance();
            projectBalanceAfter = projectBalanceBefore.subtract(amount).setScale(2, RoundingMode.HALF_UP);
            boolean projectUpdated = updateProjectAccountWithRetry(projectAccount, projectBalanceAfter, amount, true);
            if (!projectUpdated) {
                throw exception(ENERGY_ACCOUNT_VERSION_CONFLICT);
            }
            projectAccountId = projectAccount.getId();
        }

        // 4. 写流水
        accountFlowMapper.insert(EnergyAccountFlowDO.builder()
                .accountId(account.getId())
                .projectAccountId(projectAccountId)
                .flowType(FlowTypeEnum.DEDUCTION.getType())
                .amount(amount)
                .balanceBefore(balanceBefore)
                .balanceAfter(balanceAfter)
                .projectBalanceBefore(projectBalanceBefore)
                .projectBalanceAfter(projectBalanceAfter)
                .bizType(BizTypeEnum.HYDROGEN_DEDUCTION.getType())
                .bizId(bizId)
                .bizCode(bizCode)
                .remark(remark)
                .build());

        // 5. 更新账户状态
        updateAccountStatus(account.getId(), balanceAfter);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recharge(Long customerId, BigDecimal amount, Long bizId, String bizCode, String remark) {
        EnergyAccountDO account = getOrCreateByCustomerId(customerId);
        BigDecimal balanceBefore = account.getBalance();
        BigDecimal balanceAfter = balanceBefore.add(amount).setScale(2, RoundingMode.HALF_UP);

        // Optimistic lock update
        int retryCount = 0;
        while (retryCount < 3) {
            int rows = accountMapper.update(null, new LambdaUpdateWrapper<EnergyAccountDO>()
                    .set(EnergyAccountDO::getBalance, balanceAfter)
                    .set(EnergyAccountDO::getAccumulatedRecharge, account.getAccumulatedRecharge().add(amount))
                    .set(EnergyAccountDO::getLastRechargeDate, LocalDate.now())
                    .set(EnergyAccountDO::getVersion, account.getVersion() + 1)
                    .eq(EnergyAccountDO::getId, account.getId())
                    .eq(EnergyAccountDO::getVersion, account.getVersion()));
            if (rows > 0) break;
            retryCount++;
            account = accountMapper.selectById(account.getId());
            balanceBefore = account.getBalance();
            balanceAfter = balanceBefore.add(amount).setScale(2, RoundingMode.HALF_UP);
        }
        if (retryCount >= 3) {
            throw exception(ENERGY_ACCOUNT_VERSION_CONFLICT);
        }

        // Write flow
        accountFlowMapper.insert(EnergyAccountFlowDO.builder()
                .accountId(account.getId())
                .flowType(FlowTypeEnum.RECHARGE.getType())
                .amount(amount)
                .balanceBefore(balanceBefore)
                .balanceAfter(balanceAfter)
                .bizType(BizTypeEnum.RECHARGE.getType())
                .bizId(bizId)
                .bizCode(bizCode)
                .remark(remark)
                .build());

        // Update status
        updateAccountStatus(account.getId(), balanceAfter);
    }

    @Override
    public void updateThreshold(Long id, BigDecimal threshold) {
        EnergyAccountDO account = accountMapper.selectById(id);
        if (account == null) {
            throw exception(ENERGY_ACCOUNT_NOT_EXISTS);
        }
        accountMapper.update(null, new LambdaUpdateWrapper<EnergyAccountDO>()
                .set(EnergyAccountDO::getReminderThreshold, threshold)
                .eq(EnergyAccountDO::getId, id));
    }

    @Override
    public PageResult<EnergyAccountFlowDO> getFlowPage(EnergyAccountFlowPageReqVO reqVO) {
        return accountFlowMapper.selectPage(reqVO);
    }

    @Override
    public List<EnergyAccountDO> getAccountList() {
        return accountMapper.selectList();
    }

    @Override
    public EnergyAccountSummaryVO getAccountSummary() {
        List<EnergyAccountDO> accounts = accountMapper.selectList();
        EnergyAccountSummaryVO vo = new EnergyAccountSummaryVO();
        vo.setTotalCount(accounts.size());
        vo.setTotalBalance(accounts.stream()
                .map(EnergyAccountDO::getBalance)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        vo.setTotalRecharge(accounts.stream()
                .map(EnergyAccountDO::getAccumulatedRecharge)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        vo.setWarningCount((int) accounts.stream()
                .filter(a -> a.getReminderThreshold() != null
                        && a.getBalance().compareTo(a.getReminderThreshold()) <= 0)
                .count());
        return vo;
    }

    // ===== Private helpers =====

    private boolean updateAccountWithRetry(EnergyAccountDO account, BigDecimal balanceAfter, BigDecimal amount, boolean isDeduction) {
        int retryCount = 0;
        while (retryCount < 3) {
            LambdaUpdateWrapper<EnergyAccountDO> wrapper = new LambdaUpdateWrapper<EnergyAccountDO>()
                    .set(EnergyAccountDO::getBalance, balanceAfter)
                    .set(EnergyAccountDO::getAccumulatedHydrogen, account.getAccumulatedHydrogen().add(amount))
                    .set(EnergyAccountDO::getAccumulatedConsume, account.getAccumulatedConsume().add(amount))
                    .set(EnergyAccountDO::getVersion, account.getVersion() + 1)
                    .eq(EnergyAccountDO::getId, account.getId())
                    .eq(EnergyAccountDO::getVersion, account.getVersion());
            int rows = accountMapper.update(null, wrapper);
            if (rows > 0) return true;
            retryCount++;
            account = accountMapper.selectById(account.getId());
            BigDecimal newBalance = account.getBalance().subtract(amount).setScale(2, RoundingMode.HALF_UP);
            // Update for next iteration
            balanceAfter = newBalance;
        }
        return false;
    }

    private boolean updateProjectAccountWithRetry(EnergyProjectAccountDO projectAccount, BigDecimal balanceAfter, BigDecimal amount, boolean isDeduction) {
        int retryCount = 0;
        while (retryCount < 3) {
            LambdaUpdateWrapper<EnergyProjectAccountDO> wrapper = new LambdaUpdateWrapper<EnergyProjectAccountDO>()
                    .set(EnergyProjectAccountDO::getProjectBalance, balanceAfter)
                    .set(EnergyProjectAccountDO::getProjectHydrogenAmount, projectAccount.getProjectHydrogenAmount().add(amount))
                    .set(EnergyProjectAccountDO::getProjectConsumeAmount, projectAccount.getProjectConsumeAmount().add(amount))
                    .set(EnergyProjectAccountDO::getVersion, projectAccount.getVersion() + 1)
                    .eq(EnergyProjectAccountDO::getId, projectAccount.getId())
                    .eq(EnergyProjectAccountDO::getVersion, projectAccount.getVersion());
            int rows = projectAccountMapper.update(null, wrapper);
            if (rows > 0) return true;
            retryCount++;
            projectAccount = projectAccountMapper.selectById(projectAccount.getId());
            balanceAfter = projectAccount.getProjectBalance().subtract(amount).setScale(2, RoundingMode.HALF_UP);
        }
        return false;
    }

    private void updateAccountStatus(Long accountId, BigDecimal currentBalance) {
        EnergyAccountDO account = accountMapper.selectById(accountId);
        Integer newStatus;
        if (currentBalance.compareTo(BigDecimal.ZERO) < 0) {
            newStatus = AccountStatusEnum.OVERDUE.getStatus();
        } else if (account.getReminderThreshold() != null
                && currentBalance.compareTo(account.getReminderThreshold()) <= 0) {
            newStatus = AccountStatusEnum.WARNING.getStatus();
        } else {
            newStatus = AccountStatusEnum.NORMAL.getStatus();
        }
        if (!newStatus.equals(account.getAccountStatus())) {
            accountMapper.update(null, new LambdaUpdateWrapper<EnergyAccountDO>()
                    .set(EnergyAccountDO::getAccountStatus, newStatus)
                    .eq(EnergyAccountDO::getId, accountId));
        }
    }
}
