package org.dromara.main.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayFundTransUniTransferRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.main.config.WithdrawConfig;
import org.dromara.main.domain.CompanyAccountFlow;
import org.dromara.main.domain.PaymentConfig;
import org.dromara.main.domain.Withdraw;
import org.dromara.main.domain.WithdrawAccount;
import org.dromara.main.domain.bo.WithdrawBo;
import org.dromara.main.domain.vo.WithdrawAccountVo;
import org.dromara.main.domain.vo.WithdrawVo;
import org.dromara.main.mapper.CompanyAccountFlowMapper;
import org.dromara.main.mapper.WithdrawAccountMapper;
import org.dromara.main.mapper.WithdrawMapper;
import org.dromara.main.service.IPaymentConfigService;
import org.dromara.main.service.IWithdrawService;
import org.dromara.system.domain.SysTenant;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.mapper.SysTenantMapper;
import org.dromara.system.service.ISysTenantService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 门户提现申请服务
 */
@RequiredArgsConstructor
@Service
public class WithdrawServiceImpl implements IWithdrawService {

    private final WithdrawMapper withdrawMapper;
    private final WithdrawAccountMapper withdrawAccountMapper;
    private final CompanyAccountFlowMapper companyAccountFlowMapper;
    private final ISysTenantService tenantService;
    private final SysTenantMapper sysTenantMapper;
    private final IPaymentConfigService paymentConfigService;
    private final WithdrawConfig withdrawConfig;

    @Override
    public WithdrawVo queryById(Long id) {
        WithdrawVo vo = withdrawMapper.selectVoById(id);
        if (vo != null) {
            fillVoList(List.of(vo));
        }
        return vo;
    }

    @Override
    public TableDataInfo<WithdrawVo> queryPageList(WithdrawBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<Withdraw> lqw = buildQueryWrapper(bo);
        Page<WithdrawVo> result = withdrawMapper.selectVoPage(pageQuery.build(), lqw);
        fillVoList(result.getRecords());
        return TableDataInfo.build(result);
    }

    @Override
    public List<WithdrawVo> queryList(WithdrawBo bo) {
        List<WithdrawVo> list = withdrawMapper.selectVoList(buildQueryWrapper(bo));
        fillVoList(list);
        return list;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long applyWithdraw(WithdrawBo bo) {
        SysTenantVo company = getCurrentCompany();
        BigDecimal amount = defaultAmount(bo.getAmount());
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("提现金额必须大于0");
        }

        BigDecimal availableBalance = defaultAmount(company.getAvailableBalance());
        if (availableBalance.compareTo(amount) < 0) {
            throw new ServiceException("可使用余额不足，当前可用余额：{}元", availableBalance);
        }

        WithdrawAccount account = withdrawAccountMapper.selectById(bo.getAccountId());
        if (account == null) {
            throw new ServiceException("收款账户不存在");
        }
        if (!Objects.equals(account.getCompanyId(), company.getId())) {
            throw new ServiceException("收款账户不属于当前企业");
        }
        if (!Objects.equals(account.getStatus(), 1)) {
            throw new ServiceException("收款账户未审核通过");
        }

        Withdraw withdraw = new Withdraw();
        withdraw.setWithdrawNo(generateWithdrawNo());
        withdraw.setCompanyId(company.getId());
        withdraw.setAccountId(account.getId());
        withdraw.setWithdrawAmount(amount);
        withdraw.setWithdrawStatus(0);
        withdrawMapper.insert(withdraw);

        BigDecimal withdrawingBalance = defaultAmount(company.getWithdrawingBalance());

        SysTenant update = new SysTenant();
        update.setId(company.getId());
        update.setAvailableBalance(availableBalance.subtract(amount));
        update.setWithdrawingBalance(withdrawingBalance.add(amount));
        sysTenantMapper.updateById(update);

        insertFlow(company.getId(), 2, amount, 1, availableBalance, availableBalance.subtract(amount), 5, withdraw.getId(), withdraw.getWithdrawNo(), "申请提现：" + withdraw.getWithdrawNo());
        insertFlow(company.getId(), 1, amount, 3, withdrawingBalance, withdrawingBalance.add(amount), 5, withdraw.getId(), withdraw.getWithdrawNo(), "申请提现（冻结）：" + withdraw.getWithdrawNo());

        if (withdrawConfig.isAutoAudit() && shouldAutoAudit(company.getId(), amount)) {
            try {
                auditAndTransfer(withdraw.getId(), "系统自动审核通过");
            } catch (Exception ignored) {
                // 自动审核失败时保留待审核状态，转人工处理
            }
        }

        return withdraw.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean auditAndTransfer(Long id, String auditRemark) {
        Withdraw withdraw = withdrawMapper.selectById(id);
        if (withdraw == null) {
            throw new ServiceException("提现申请不存在");
        }
        if (!Objects.equals(withdraw.getWithdrawStatus(), 0)) {
            throw new ServiceException("提现状态不正确，当前状态：{}", withdraw.getWithdrawStatus());
        }

        withdraw.setWithdrawStatus(1);
        withdraw.setAuditorId(LoginHelper.getUserId());
        withdraw.setAuditTime(new Date());
        withdraw.setAuditRemark(auditRemark);
        withdrawMapper.updateById(withdraw);

        try {
            String tradeNo = transferToAlipay(withdraw);

            SysTenant tenant = sysTenantMapper.selectById(withdraw.getCompanyId());
            if (tenant == null) {
                throw new ServiceException("企业信息不存在");
            }
            BigDecimal withdrawingBalance = defaultAmount(tenant.getWithdrawingBalance());
            if (withdrawingBalance.compareTo(withdraw.getWithdrawAmount()) < 0) {
                throw new ServiceException("提现中余额不足");
            }
            SysTenant update = new SysTenant();
            update.setId(tenant.getId());
            update.setWithdrawingBalance(withdrawingBalance.subtract(withdraw.getWithdrawAmount()));
            sysTenantMapper.updateById(update);

            insertFlow(tenant.getId(), 2, withdraw.getWithdrawAmount(), 3, withdrawingBalance,
                withdrawingBalance.subtract(withdraw.getWithdrawAmount()), 6, withdraw.getId(), withdraw.getWithdrawNo(), "提现成功：" + withdraw.getWithdrawNo());

            withdraw.setWithdrawStatus(3);
            withdraw.setTradeNo(tradeNo);
            withdraw.setTransferTime(new Date());
            withdrawMapper.updateById(withdraw);
            return true;
        } catch (Exception e) {
            refundWithdrawToAvailable(withdraw, "提现失败，退回可使用余额");
            withdraw.setWithdrawStatus(4);
            withdraw.setFailReason(e.getMessage());
            withdrawMapper.updateById(withdraw);
            throw new ServiceException("打款失败：{}", e.getMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean auditReject(Long id, String auditRemark) {
        Withdraw withdraw = withdrawMapper.selectById(id);
        if (withdraw == null) {
            throw new ServiceException("提现申请不存在");
        }
        if (!Objects.equals(withdraw.getWithdrawStatus(), 0)) {
            throw new ServiceException("提现状态不正确");
        }

        refundWithdrawToAvailable(withdraw, "提现审核拒绝，退回可使用余额");

        withdraw.setWithdrawStatus(5);
        withdraw.setAuditorId(LoginHelper.getUserId());
        withdraw.setAuditTime(new Date());
        withdraw.setAuditRemark(auditRemark);
        withdrawMapper.updateById(withdraw);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String handleAlipayTransferNotify(Map<String, String> params) {
        try {
            PaymentConfig config = paymentConfigService.getEnabledTransferConfig();
            if (config == null) {
                return "failure";
            }

            boolean signVerified = AlipaySignature.rsaCheckV1(
                params,
                config.getPublicKey(),
                defaultIfBlank(config.getCharset(), "UTF-8"),
                defaultIfBlank(config.getSignType(), "RSA2")
            );
            if (!signVerified) {
                return "failure";
            }

            String outBizNo = params.get("out_biz_no");
            String orderId = params.get("order_id");
            String status = params.get("status");
            String transAmount = params.get("trans_amount");

            Withdraw withdraw = withdrawMapper.selectOne(Wrappers.<Withdraw>lambdaQuery()
                .eq(Withdraw::getWithdrawNo, outBizNo)
                .last("limit 1"));
            if (withdraw == null) {
                return "failure";
            }
            if (Objects.equals(withdraw.getWithdrawStatus(), 3)) {
                return "success";
            }

            BigDecimal notifyAmount = new BigDecimal(transAmount);
            if (notifyAmount.compareTo(withdraw.getWithdrawAmount()) != 0) {
                return "failure";
            }

            if ("SUCCESS".equals(status)) {
                withdraw.setTradeNo(orderId);
                withdraw.setWithdrawStatus(3);
                withdraw.setTransferTime(new Date());
                withdrawMapper.updateById(withdraw);
                return "success";
            }

            if ("FAIL".equals(status)) {
                refundWithdrawToAvailable(withdraw, "支付宝转账失败，退回可使用余额");
                withdraw.setWithdrawStatus(4);
                withdraw.setFailReason("支付宝转账失败");
                withdrawMapper.updateById(withdraw);
                return "success";
            }
            return "success";
        } catch (Exception e) {
            return "failure";
        }
    }

    private LambdaQueryWrapper<Withdraw> buildQueryWrapper(WithdrawBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<Withdraw> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getCompanyId() != null, Withdraw::getCompanyId, bo.getCompanyId());
        lqw.eq(bo.getAccountId() != null, Withdraw::getAccountId, bo.getAccountId());
        lqw.eq(bo.getWithdrawStatus() != null, Withdraw::getWithdrawStatus, bo.getWithdrawStatus());
        lqw.eq(bo.getWithdrawNo() != null && !bo.getWithdrawNo().isBlank(), Withdraw::getWithdrawNo, bo.getWithdrawNo());
        lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
            Withdraw::getCreateTime, params.get("beginTime"), params.get("endTime"));
        lqw.orderByDesc(Withdraw::getCreateTime);
        return lqw;
    }

    private void fillVoList(List<WithdrawVo> voList) {
        if (voList == null || voList.isEmpty()) {
            return;
        }

        List<Long> accountIds = voList.stream()
            .map(WithdrawVo::getAccountId)
            .filter(Objects::nonNull)
            .distinct()
            .toList();

        Map<Long, WithdrawAccount> accountMap = accountIds.isEmpty()
            ? Map.of()
            : withdrawAccountMapper.selectList(
                    Wrappers.<WithdrawAccount>lambdaQuery().in(WithdrawAccount::getId, accountIds))
                .stream()
                .collect(Collectors.toMap(WithdrawAccount::getId, Function.identity(), (left, right) -> left));

        SysTenantVo company = getCurrentCompany();
        for (WithdrawVo vo : voList) {
            vo.setCompanyName(company.getCompanyName());
            WithdrawAccount account = accountMap.get(vo.getAccountId());
            if (account != null) {
                WithdrawAccountVo accountVo = new WithdrawAccountVo();
                accountVo.setId(account.getId());
                accountVo.setCompanyId(account.getCompanyId());
                accountVo.setAccountType(account.getAccountType());
                accountVo.setAccountName(account.getAccountName());
                accountVo.setAccountNumber(account.getAccountNumber());
                accountVo.setBankName(account.getBankName());
                accountVo.setBankBranch(account.getBankBranch());
                accountVo.setIsDefault(account.getIsDefault());
                accountVo.setStatus(account.getStatus());
                accountVo.setAuditorId(account.getAuditorId());
                accountVo.setAuditTime(account.getAuditTime());
                accountVo.setAuditRemark(account.getAuditRemark());
                accountVo.setCreateTime(account.getCreateTime());
                vo.setAccount(accountVo);
            }
        }
    }

    private SysTenantVo getCurrentCompany() {
        String tenantId = LoginHelper.getTenantId();
        if (tenantId == null || tenantId.isBlank()) {
            throw new ServiceException("未登录或token已失效");
        }
        SysTenantVo company = tenantService.queryByTenantId(tenantId);
        if (company == null) {
            throw new ServiceException("企业信息不存在");
        }
        return company;
    }

    private BigDecimal defaultAmount(BigDecimal amount) {
        return amount == null ? BigDecimal.ZERO : amount;
    }

    private void refundWithdrawToAvailable(Withdraw withdraw, String remark) {
        SysTenant tenant = sysTenantMapper.selectById(withdraw.getCompanyId());
        if (tenant == null) {
            throw new ServiceException("企业信息不存在");
        }
        BigDecimal withdrawingBalance = defaultAmount(tenant.getWithdrawingBalance());
        BigDecimal availableBalance = defaultAmount(tenant.getAvailableBalance());
        if (withdrawingBalance.compareTo(withdraw.getWithdrawAmount()) < 0) {
            throw new ServiceException("提现中余额不足");
        }

        SysTenant update = new SysTenant();
        update.setId(tenant.getId());
        update.setWithdrawingBalance(withdrawingBalance.subtract(withdraw.getWithdrawAmount()));
        update.setAvailableBalance(availableBalance.add(withdraw.getWithdrawAmount()));
        sysTenantMapper.updateById(update);

        insertFlow(tenant.getId(), 2, withdraw.getWithdrawAmount(), 3, withdrawingBalance,
            withdrawingBalance.subtract(withdraw.getWithdrawAmount()), 7, withdraw.getId(), withdraw.getWithdrawNo(), remark + "（解冻）");
        insertFlow(tenant.getId(), 1, withdraw.getWithdrawAmount(), 1, availableBalance,
            availableBalance.add(withdraw.getWithdrawAmount()), 7, withdraw.getId(), withdraw.getWithdrawNo(), remark);
    }

    private boolean shouldAutoAudit(Long companyId, BigDecimal amount) {
        WithdrawConfig.AutoAuditRule rule = withdrawConfig.getAutoAuditRule();
        if (rule == null) {
            return false;
        }

        if (amount.compareTo(defaultAmount(rule.getMaxAmount())) > 0) {
            return false;
        }

        DailyLimit dailyLimit = checkDailyLimit(companyId);
        Integer maxDailyCount = rule.getMaxDailyCount() == null ? 0 : rule.getMaxDailyCount();
        if (maxDailyCount > 0 && dailyLimit.getCount() >= maxDailyCount) {
            return false;
        }

        BigDecimal maxDailyAmount = defaultAmount(rule.getMaxDailyAmount());
        BigDecimal totalAmount = dailyLimit.getAmount().add(amount);
        return maxDailyAmount.compareTo(BigDecimal.ZERO) <= 0 || totalAmount.compareTo(maxDailyAmount) <= 0;
    }

    private DailyLimit checkDailyLimit(Long companyId) {
        LocalDate today = LocalDate.now();
        Date startOfDay = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date endOfDay = Date.from(today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());

        List<Withdraw> todayWithdraws = withdrawMapper.selectList(
            Wrappers.<Withdraw>lambdaQuery()
                .eq(Withdraw::getCompanyId, companyId)
                .ge(Withdraw::getCreateTime, startOfDay)
                .lt(Withdraw::getCreateTime, endOfDay)
        );

        int count = todayWithdraws.size();
        BigDecimal totalAmount = todayWithdraws.stream()
            .map(Withdraw::getWithdrawAmount)
            .filter(Objects::nonNull)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        return new DailyLimit(count, totalAmount);
    }

    private String transferToAlipay(Withdraw withdraw) throws Exception {
        PaymentConfig config = paymentConfigService.getEnabledTransferConfig();
        if (config == null) {
            throw new ServiceException("未找到启用的付款配置，请先配置支付宝付款参数");
        }

        WithdrawAccount account = withdrawAccountMapper.selectById(withdraw.getAccountId());
        if (account == null) {
            throw new ServiceException("收款账户不存在");
        }

        AlipayClient client = new DefaultAlipayClient(
            config.getGatewayUrl(),
            config.getAppId(),
            config.getPrivateKey(),
            defaultIfBlank(config.getFormat(), "json"),
            defaultIfBlank(config.getCharset(), "UTF-8"),
            config.getPublicKey(),
            defaultIfBlank(config.getSignType(), "RSA2")
        );

        AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
        if (config.getTransferNotifyUrl() != null) {
            request.setNotifyUrl(config.getTransferNotifyUrl());
        }

        JSONObject bizContent = JSONUtil.createObj();
        bizContent.set("out_biz_no", withdraw.getWithdrawNo());
        bizContent.set("trans_amount", withdraw.getWithdrawAmount().toString());
        bizContent.set("product_code", "TRANS_ACCOUNT_NO_PWD");
        bizContent.set("biz_scene", "DIRECT_TRANSFER");

        JSONObject payeeInfo = JSONUtil.createObj();
        payeeInfo.set("identity", account.getAccountNumber());
        payeeInfo.set("identity_type", "ALIPAY_LOGON_ID");
        payeeInfo.set("name", account.getAccountName());
        bizContent.set("payee_info", payeeInfo);
        bizContent.set("remark", "平台背调佣金提现");

        request.setBizContent(bizContent.toString());

        AlipayFundTransUniTransferResponse response = client.execute(request);
        if (response.isSuccess()) {
            return response.getOrderId();
        }
        throw new ServiceException("支付宝返回错误：{}", response.getSubMsg());
    }

    private void insertFlow(Long companyId, Integer flowType, BigDecimal amount, Integer balanceType,
                            BigDecimal balanceBefore, BigDecimal balanceAfter, Integer businessType,
                            Long businessId, String businessNo, String remark) {
        CompanyAccountFlow flow = new CompanyAccountFlow();
        flow.setCompanyId(companyId);
        flow.setFlowNo(generateFlowNo());
        flow.setFlowType(flowType);
        flow.setAmount(amount);
        flow.setBalanceType(balanceType);
        flow.setBalanceBefore(balanceBefore);
        flow.setBalanceAfter(balanceAfter);
        flow.setBusinessType(businessType);
        flow.setBusinessId(businessId);
        flow.setBusinessNo(businessNo);
        flow.setRemark(remark);
        companyAccountFlowMapper.insert(flow);
    }

    private String generateFlowNo() {
        return "CF" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4);
    }

    private String generateWithdrawNo() {
        return "WD" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4);
    }

    private String defaultIfBlank(String value, String defaultValue) {
        return value == null || value.isBlank() ? defaultValue : value;
    }

    private static class DailyLimit {
        private final int count;
        private final BigDecimal amount;

        private DailyLimit(int count, BigDecimal amount) {
            this.count = count;
            this.amount = amount;
        }

        public int getCount() {
            return count;
        }

        public BigDecimal getAmount() {
            return amount;
        }
    }
}
