package org.dromara.web.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.system.domain.*;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.mapper.*;
import org.dromara.system.service.ISysConfigService;
import org.dromara.web.domain.oneAPI.CallbackReq;
import org.dromara.web.domain.oneAPI.PreCheckReq;
import org.dromara.web.domain.oneAPI.PreCheckResp;
import org.dromara.web.service.IAiBillingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Random;

@Slf4j
@Service
public class AiBillingServiceImpl implements IAiBillingService {

    @Autowired
    private SysTenantMapper tenantMapper;
    @Autowired
    private AiUserBalanceMapper userBalanceMapper;
    @Autowired
    private AiFreezeRecordMapper freezeRecordMapper;
    @Autowired
    private AiModelPriceMapper modelPriceMapper;
    @Autowired
    private AiExpenseRecordMapper expenseRecordMapper;
    @Autowired
    private ISysConfigService sysConfigService;

    @Value("${ruoyi.callback.secret:AIGateWay76428398263229873982}")
    private String callbackSecret;

    // 冻结记录状态（沿用现有语义）
    private static final int FREEZE_STATUS_ACTIVE = 0; // 待处理（冻结中）
    private static final int FREEZE_STATUS_SETTLED = 1; // 已结算
    private static final int FREEZE_STATUS_REFUNDED = 2; // 已退款

    // 消费记录状态。
    private static final int EXPENSE_STATUS_SUCCESS = 0;
    private static final int EXPENSE_STATUS_FAILED = 1;

    // ==================== preCheck ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<PreCheckResp> preCheck(PreCheckReq req) {
        PreCheckResp resp = new PreCheckResp();
        resp.setTenantStatus(false);
        resp.setWalletStatus(false);
        resp.setAllow(false);

        // 1. 参数校验
        if (req.getTenantId() == null || req.getTenantId().trim().isEmpty()) {
            resp.setMessage("tenantId 不能为空");
            return R.ok(resp);
        }
        if (req.getModel() == null || req.getModel().trim().isEmpty()) {
            resp.setMessage("model 不能为空");
            return R.ok(resp);
        }
        if (req.getRequestId() == null || req.getRequestId().trim().isEmpty()) {
            resp.setMessage("requestId 不能为空");
            return R.ok(resp);
        }

        // 2. 查租户
        SysTenantVo tenant = tenantMapper.selectVoOne(
            Wrappers.lambdaQuery(SysTenant.class)
                .eq(SysTenant::getTenantId, req.getTenantId())
        );
        if (tenant == null) {
            resp.setMessage("租户不存在");
            return R.ok(resp);
        }
        if (!"0".equals(String.valueOf(tenant.getStatus()))) {
            resp.setMessage("租户已禁用");
            return R.ok(resp);
        }
        // ruoyi 租户状态：0 正常，1 停用
        resp.setTenantStatus(true);

        // 3. 查钱包
        AiUserBalance balance = userBalanceMapper.selectByTenantId(req.getTenantId());
        if (balance == null) {
            resp.setMessage("用户余额账户异常");
            return R.ok(resp);
        }
        if (balance.getStatus() == null || balance.getStatus() != 0) {
            resp.setMessage("钱包已冻结");
            return R.ok(resp);
        }
        resp.setWalletStatus(true);

        // 4. 查余额
        Long currentBalance = balance.getBalance();
        if (currentBalance == null) {
            resp.setMessage("余额查询失败");
            return R.ok(resp);
        }
        resp.setTenantBalance(currentBalance);

        // 5. 计算预估费用
        long estimatedCost;
        if ("video".equals(req.getTaskType()) || "image".equals(req.getTaskType())) {
            // 视频/图片：读 sys_config 固定价
            String configKey = "freeze_" + req.getTaskType();
            String configValue = sysConfigService.getConfigByKey(configKey);
            if (configValue == null || configValue.trim().isEmpty()) {
                resp.setMessage("系统配置缺失: " + configKey);
                return R.ok(resp);
            }
            try {
                estimatedCost = Long.parseLong(configValue.trim());
            } catch (NumberFormatException e) {
                resp.setMessage("系统配置格式错误: " + configValue);
                return R.ok(resp);
            }
            resp.setInputPrice(0L);
            resp.setOutputPrice(estimatedCost);
        } else {
            // 文本：查模型定价
            AiModelPrice price = modelPriceMapper.selectOne(
                Wrappers.<AiModelPrice>lambdaQuery()
                    .eq(AiModelPrice::getModelCode, req.getModel())
                    .eq(AiModelPrice::getStatus, 0)
                    .last("limit 1")
            );
            if (price == null) {
                resp.setMessage("模型价格未配置: " + req.getModel());
                return R.ok(resp);
            }
            resp.setInputPrice(price.getInputPrice());
            resp.setOutputPrice(price.getOutputPrice());

            // 计算：四舍五入
            long inputCost = calcCost(req.getInputTokens() != null ? req.getInputTokens() : 0, price.getInputPrice());
            long outputCost = calcCost(req.getMaxTokens() != null ? req.getMaxTokens() : 0, price.getOutputPrice());
            estimatedCost = inputCost + outputCost;
        }
        resp.setEstimatedCost(estimatedCost);

        // 6. 余额检查
        if (currentBalance < estimatedCost) {
            resp.setMessage(String.format("余额不足: 预计需要 %d, 剩余 %d", estimatedCost, currentBalance));
            return R.ok(resp);
        }

        // 7. 数据库扣减（原子操作，行锁防并发透支）
        int rows = userBalanceMapper.decrBalance(req.getTenantId(), estimatedCost);
        if (rows == 0) {
            // 并发场景：余额刚好被其他请求扣完
            resp.setMessage("用户账号预扣异常");
            return R.ok(resp);
        }

        // 8. 写入冻结记录
        String freezeId = "frz_" + System.currentTimeMillis() + "_" + new Random().nextInt(10000);
        AiFreezeRecord record = new AiFreezeRecord();
        record.setFreezeId(freezeId);
        record.setRequestId(req.getRequestId());
        record.setTenantId(req.getTenantId());
        record.setModelCode(req.getModel());
        record.setEstimatedCost(estimatedCost);
        record.setActualCost(0L);
        record.setStatus(0);
        freezeRecordMapper.insert(record);

        resp.setAllow(true);
        resp.setMessage("ok");

        log.info("[Billing] preCheck allow, freezeId={}, requestId={}, tenantId={}, model={}, taskType={}, cost={}",
            freezeId, req.getRequestId(), req.getTenantId(), req.getModel(), req.getTaskType(), estimatedCost);

        return R.ok(resp);
    }

    // ==================== callback ====================
//
// 回调状态机（oneAPI 侧已做"退款收口"，正常情况下同一 requestId 只会
// 收到一种终态；本状态机是乱序/补偿场景的兜底防御）：
//
//   已有记录          本次回调     处理
//   无 + 冻结有效     failed       全额退款
//   无 + 冻结有效     succeeded    结算差额（多退少补）
//   failed 记录       failed       幂等返回
//   succeeded 记录    succeeded    幂等返回
//   failed 记录       succeeded    补结算：按实际费用直接扣款（冻结已退）
//   succeeded 记录    failed       拒绝退款，告警人工介入
//   无 + 无有效冻结   succeeded    补结算（边界：冻结被 TTL 任务提前释放）
//   无 + 无有效冻结   failed       幂等返回
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Void> callback(CallbackReq req) {
        // 1. 验签
        if (req.getSecret() == null || !callbackSecret.equals(req.getSecret())) {
            log.warn("[Billing] callback 签名校验失败, requestId={}", req.getRequestId());
            return R.fail("签名校验失败");
        }

        boolean isSuccess = "succeeded".equals(req.getStatus()) || "done".equals(req.getStatus());

        // 2. 状态机幂等：查已有消费记录（取实体，不再只 count）
        AiExpenseRecord exist = expenseRecordMapper.selectOne(
            Wrappers.<AiExpenseRecord>lambdaQuery()
                .eq(AiExpenseRecord::getRequestId, req.getRequestId())
                .last("limit 1")
        );
        if (exist != null) {
            boolean existSuccess = exist.getStatus() != null
                && exist.getStatus() == EXPENSE_STATUS_SUCCESS;
            if (existSuccess == isSuccess) {
                // 同状态重复回调：真幂等（outbox 重试/网络重发都会走到这里）
                log.info("[Billing] callback 幂等拦截(同状态), requestId={}, status={}",
                    req.getRequestId(), req.getStatus());
                return R.ok();
            }
            if (existSuccess) {
                // 已结算成功又收到 failed：绝对不能退款（退了就是白送钱）。发生即异常，告警人工介入。
                log.error("[Billing] 已结算记录收到 failed 回调，拒绝退款，需人工核查, requestId={}",
                    req.getRequestId());
                return R.ok();
            }
            // 之前 failed（已全额退款）、现在 succeeded：补结算，直接按实际费用扣款
            log.info("[Billing] failed 后收到 succeeded，进入补结算, requestId={}", req.getRequestId());
            return settleWithoutFreeze(req, exist);
        }

        // 3. 无消费记录：查冻结记录（按 requestId + tenantId，不依赖前端传 freezeId）
        AiFreezeRecord freeze = freezeRecordMapper.selectOne(
            Wrappers.<AiFreezeRecord>lambdaQuery()
                .eq(AiFreezeRecord::getRequestId, req.getRequestId())
                .eq(AiFreezeRecord::getTenantId, req.getTenantId())
                .last("limit 1")
        );
        if (freeze == null || (freeze.getStatus() != null && freeze.getStatus() != FREEZE_STATUS_ACTIVE)) {
            if (!isSuccess) {
                // 没冻结（或已处理过）+ failed：没有可退的，幂等返回
                log.info("[Billing] 没有可退的冻结, requestId={}", req.getRequestId());
                return R.ok();
            }
            // 无有效冻结但报成功（边界：冻结已被 TTL 定时任务释放）：
            // 钱还是要收，按补结算直接扣实际费用
            log.warn("[Billing] 无有效冻结但收到 succeeded，进入补结算, requestId={}, freezeStatus={}",
                req.getRequestId(), freeze == null ? "null" : freeze.getStatus());
            return settleWithoutFreeze(req, null);
        }

        // 4. 查模型价格（必须判空：查不到价格直接抛异常让事务回滚，
        //    oneAPI outbox 会按退避重试；静默 NPE 会写出脏数据/卡死回调）
        AiModelPrice price = modelPriceMapper.selectOne(
            Wrappers.<AiModelPrice>lambdaQuery()
                .eq(AiModelPrice::getModelCode, req.getModel())
                .eq(AiModelPrice::getStatus, 0)
                .last("limit 1")
        );
        if (price == null) {
            log.error("[Billing] 模型价格配置缺失, model={}, requestId={}", req.getModel(), req.getRequestId());
            throw new RuntimeException("模型价格配置缺失: " + req.getModel());
        }

        long estimatedCost = freeze.getEstimatedCost();
        long actualCost = 0L;
        long refundAmount;

        if (isSuccess) {
            // 5a. 成功：按 token 计算实际费用，结算差额（多退少补）
            actualCost = calcActualCost(req, price);
            refundAmount = estimatedCost - actualCost;

            freeze.setActualCost(actualCost);
            freeze.setStatus(FREEZE_STATUS_SETTLED);
            freeze.setConfirmedBy(new Date());
            freezeRecordMapper.updateById(freeze);

            if (refundAmount > 0) {
                // 多扣了，退差额
                int refundRows = userBalanceMapper.incrBalance(req.getTenantId(), refundAmount);
                if (refundRows == 0) {
                    log.error("[Billing] 退款失败，租户不存在, tenantId={}", req.getTenantId());
                    throw new RuntimeException("退款失败，租户不存在");
                }
            } else if (refundAmount < 0) {
                // 少扣了，补扣差额。余额不足扣不动 = 资损：
                // 不阻塞用户请求，但必须告警 + 留痕供追缴
                long extraCost = -refundAmount;
                int extraRows = userBalanceMapper.decrBalance(req.getTenantId(), extraCost);
                if (extraRows == 0) {
                    log.error("[Billing] 补扣失败(余额不足)，需追缴, tenantId={}, extraCost={}, requestId={}",
                        req.getTenantId(), extraCost, req.getRequestId());
                    // TODO: 接告警/写待追缴表
                }
            }
        } else {
            // 5b. 失败：全额退款
            refundAmount = estimatedCost;
            freeze.setStatus(FREEZE_STATUS_REFUNDED);
            freeze.setRefundedBy(new Date());
            freezeRecordMapper.updateById(freeze);

            int refundRows = userBalanceMapper.incrBalance(req.getTenantId(), refundAmount);
            if (refundRows == 0) {
                log.error("[Billing] 退款失败，租户不存在, tenantId={}", req.getTenantId());
                throw new RuntimeException("退款失败，租户不存在");
            }
        }

        // 6. 写消费记录
        AiExpenseRecord record = buildExpenseRecord(req, price, actualCost, isSuccess);
        expenseRecordMapper.insert(record);

        log.info("[Billing] callback success, requestId={}, status={}, actualCost={}, refund={}",
            req.getRequestId(), req.getStatus(), actualCost, refundAmount);
        return R.ok();
    }

    // ==================== 补结算 ====================
// 场景：failed 已全额退款（或冻结不存在/已被 TTL 释放）之后又收到 succeeded。
// 没有冻结可退差额，按实际费用直接从余额扣全款。
// 余额不足扣不动 = 资损：不阻塞（视频已生成/服务已提供），但告警 + 留痕供追缴。
    private R<Void> settleWithoutFreeze(CallbackReq req, AiExpenseRecord exist) {
        AiModelPrice price = modelPriceMapper.selectOne(
            Wrappers.<AiModelPrice>lambdaQuery()
                .eq(AiModelPrice::getModelCode, req.getModel())
                .eq(AiModelPrice::getStatus, 0)
                .last("limit 1")
        );
        if (price == null) {
            log.error("[Billing] 补结算失败：模型价格配置缺失, model={}, requestId={}",
                req.getModel(), req.getRequestId());
            throw new RuntimeException("模型价格配置缺失: " + req.getModel());
        }

        long actualCost = calcActualCost(req, price);
        int rows = userBalanceMapper.decrBalance(req.getTenantId(), actualCost);
        if (rows == 0) {
            log.error("[Billing] 补结算扣款失败，需追缴, tenantId={}, actualCost={}, requestId={}",
                req.getTenantId(), actualCost, req.getRequestId());
            // TODO: 接告警/写待追缴表
        }

        if (exist != null) {
            // 更新原 failed 记录为成功（保留同一 requestId 只有一条消费记录的约束）
            exist.setStatus(EXPENSE_STATUS_SUCCESS);
            fillUsage(exist, req, price, actualCost);
            expenseRecordMapper.updateById(exist);
        } else {
            expenseRecordMapper.insert(buildExpenseRecord(req, price, actualCost, true));
        }

        log.info("[Billing] 补结算完成, requestId={}, actualCost={}, charged={}",
            req.getRequestId(), actualCost, rows > 0);
        return R.ok();
    }

// ==================== 消费记录组装 ====================

    private AiExpenseRecord buildExpenseRecord(CallbackReq req, AiModelPrice price,
                                               long actualCost, boolean isSuccess) {
        AiExpenseRecord record = new AiExpenseRecord();
        record.setRequestId(req.getRequestId());
        record.setTenantId(req.getTenantId());
        record.setModelCode(req.getModel());
        fillUsage(record, req, price, actualCost);
        record.setStatus(isSuccess ? EXPENSE_STATUS_SUCCESS : EXPENSE_STATUS_FAILED);
        if (isSuccess) {
            // 视频地址放独立字段。暂时不放
            //record.(req.getVideoUrl());
        } else {
            record.setErrorMsg(req.getErrorMsg());
        }
        return record;
    }

    private void fillUsage(AiExpenseRecord record, CallbackReq req, AiModelPrice price, long actualCost) {
        if (req.getUsage() != null) {
            record.setPromptTokens(req.getUsage().getPromptTokens() != null
                ? Long.valueOf(req.getUsage().getPromptTokens()) : 0L);
            record.setCompletionTokens(req.getUsage().getCompletionTokens() != null
                ? Long.valueOf(req.getUsage().getCompletionTokens()) : 0L);
        } else {
            record.setPromptTokens(0L);
            record.setCompletionTokens(0L);
        }
        record.setInputPrice(price.getInputPrice());
        record.setOutputPrice(price.getOutputPrice());
        record.setPromptAmount(calcCost(record.getPromptTokens().intValue(), price.getInputPrice()));
        record.setCompletionAmount(calcCost(record.getCompletionTokens().intValue(), price.getOutputPrice()));
        record.setConsumeAmount(actualCost);
    }


    // ==================== 冻结 TTL 自动释放（供定时任务调用） ====================

    /**
     * 释放单笔超时冻结。
     * 注意：必须保持"由外部 Bean 调用"（定时任务），不要在类内部自调用，否则 @Transactional 不生效。
     * <p>
     * 安全设计：
     * 1. 认领：条件 UPDATE status 0->2，更新不到说明已被 callback/其他实例处理，跳过不退款；
     * 2. 幂等：消费记录已存在说明 callback 已处理过，跳过不退款（双保险）；
     * 3. 退款与消费记录在同一事务，任何异常整体回滚，下轮任务重试。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean releaseOneExpiredFreeze(String freezeId) {
        Date now = new Date();

        // 1. 认领（多实例/与 callback 并发下只会有一个成功）
        int claimRows = freezeRecordMapper.update(null,
            Wrappers.<AiFreezeRecord>lambdaUpdate()
                .set(AiFreezeRecord::getStatus, 2)
                .set(AiFreezeRecord::getRefundedBy, now)
                .eq(AiFreezeRecord::getFreezeId, freezeId)
                .eq(AiFreezeRecord::getStatus, 0)
        );
        if (claimRows == 0) {
            return false;
        }

        AiFreezeRecord freeze = freezeRecordMapper.selectOne(
            Wrappers.<AiFreezeRecord>lambdaQuery()
                .eq(AiFreezeRecord::getFreezeId, freezeId)
        );
        if (freeze == null) {
            return false;
        }

        // 2. 幂等兜底：消费记录已存在 = callback 已处理，只清理状态不重复退款
        Long expenseCount = expenseRecordMapper.selectCount(
            Wrappers.<AiExpenseRecord>lambdaQuery()
                .eq(AiExpenseRecord::getRequestId, freeze.getRequestId())
        );
        if (expenseCount != null && expenseCount > 0) {
            log.warn("[Billing] 冻结释放时发现消费记录已存在，跳过退款, freezeId={}, requestId={}", freezeId, freeze.getRequestId());
            return false;
        }

        // 3. 退回余额
        int rows = userBalanceMapper.incrBalance(freeze.getTenantId(), freeze.getEstimatedCost());
        if (rows == 0) {
            throw new RuntimeException("冻结超时释放失败，租户不存在: " + freeze.getTenantId());
        }

        AiModelPrice price = modelPriceMapper.selectOne(
            Wrappers.<AiModelPrice>lambdaQuery()
                .eq(AiModelPrice::getModelCode, freeze.getModelCode())
                .eq(AiModelPrice::getStatus, 0)
                .last("limit 1")
        );

        // 4. 写消费记录（审计 + 借助 callback 的消费记录幂等拦截迟到的回调）
        AiExpenseRecord record = new AiExpenseRecord();
        record.setRequestId(freeze.getRequestId());
        record.setTenantId(freeze.getTenantId());
        record.setModelCode(freeze.getModelCode());
        record.setPromptTokens(0L);
        record.setCompletionTokens(0L);
        record.setInputPrice(price.getInputPrice());
        record.setOutputPrice(price.getOutputPrice());
        record.setPromptAmount(0L);
        record.setCompletionAmount(0L);
        record.setConsumeAmount(0L);
        record.setStatus(1); // 与失败退款保持一致
        record.setErrorMsg("接口调用失败，冻结余额已经全额退回");
        record.setCreateTime(freeze.getCreateTime());
        expenseRecordMapper.insert(record);

        log.info("[Billing] 冻结超时自动释放, freezeId={}, requestId={}, tenantId={}, amount={}",
            freezeId, freeze.getRequestId(), freeze.getTenantId(), freeze.getEstimatedCost());
        return true;
    }

    // ==================== 私有方法 ====================

    /**
     * 计算费用（毫厘），四舍五入
     */
    private long calcCost(int tokens, long pricePerMillion) {
        if (tokens <= 0 || pricePerMillion <= 0) {
            return 0L;
        }
        return (tokens * pricePerMillion + 500000L) / 1000000L;
    }

    /**
     * 计算实际费用
     * 视频/图片：固定价（actualCost = estimatedCost）
     * 文本：按实际 token 计算
     */
    private long calcActualCost(CallbackReq req, AiModelPrice price) {
        // 文本模型：按实际 token 计算
        if (price == null) {
            log.warn("[Billing] 模型价格未配置, model={}, 按0计费", req.getModel());
            return 0L;
        }

        int promptTokens = (req.getUsage() != null && req.getUsage().getPromptTokens() != null)
            ? req.getUsage().getPromptTokens() : 0;
        int completionTokens = (req.getUsage() != null && req.getUsage().getCompletionTokens() != null)
            ? req.getUsage().getCompletionTokens() : 0;

        long inputCost = calcCost(promptTokens, price.getInputPrice());
        long outputCost = calcCost(completionTokens, price.getOutputPrice());

        return inputCost + outputCost;
    }
}
