package com.yaoai.billing.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoai.billing.config.BillingConfig;
import com.yaoai.billing.dto.BillingChargeRequest;
import com.yaoai.billing.service.BillingCostService;
import com.yaoai.billing.service.BillingService;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.BillingRecord;
import com.yaoai.domain.entity.TenantBalance;
import com.yaoai.domain.mapper.BillingRecordMapper;
import com.yaoai.domain.mapper.TenantBalanceMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;

@Slf4j
@Service
@RequiredArgsConstructor
public class BillingServiceImpl implements BillingService {

    private final BillingConfig billingConfig;
    private final TenantBalanceMapper tenantBalanceMapper;
    private final BillingRecordMapper billingRecordMapper;
    private final ObjectMapper objectMapper;
    private final BillingCostService billingCostService;

    @Override
    @Transactional
    @CacheEvict(value = "balance", key = "#request.tenantId")
    public void charge(BillingChargeRequest request) {
        if (billingConfig.isOff()) return;

        boolean isSuccess = isSuccess(request);
        BigDecimal cost = estimateCost(request);

        if (cost.compareTo(BigDecimal.ZERO) > 0 && billingConfig.isStrict() && isSuccess) {
            int rows = tenantBalanceMapper.tryDeduct(request.getTenantId(), cost);
            if (rows == 0) {
                throwInsufficientBalance();
            }
        }

        BillingRecord r = new BillingRecord();
        r.setTenantId(request.getTenantId());
        r.setUserId(request.getUserId());
        r.setProjectId(request.getProjectId());
        r.setOperation(request.getOperation());
        r.setModality(request.getModality());
        r.setModelProvider(request.getModelProvider());
        r.setModelId(request.getModelId());
        r.setModelName(request.getModelName());
        r.setUnitCount(request.getUnitCount() == null ? 1 : request.getUnitCount());
        r.setSuccess(isSuccess);
        r.setCredits(cost);
        r.setBillingMode(billingMode());
        r.setRefId(request.getRefId());
        if (request.getMeta() != null) {
            r.setBillingMeta(toJson(request));
        }
        billingRecordMapper.insert(r);

        log.debug("Billed: tenant={}, op={}, modality={}, model={}, credits={}, success={}",
                request.getTenantId(), request.getOperation(), request.getModality(),
                request.getModelId(), cost, isSuccess);
    }

    @Override
    @Deprecated
    @Transactional
    @CacheEvict(value = "balance", key = "#tenantId")
    public void charge(Long tenantId, Long projectId, String operation, String refId) {
        BillingService.super.charge(tenantId, projectId, operation, refId);
    }

    @Override
    public BigDecimal estimateCost(BillingChargeRequest request) {
        if (billingConfig.isOff() || !isSuccess(request)) {
            return BigDecimal.ZERO;
        }
        return request.getCredits() != null
                ? request.getCredits()
                : billingCostService.getCost(request.getOperation());
    }

    @Override
    public void checkBalance(BillingChargeRequest request) {
        checkBalance(request.getTenantId(), estimateCost(request));
    }

    @Override
    public void checkBalance(Long tenantId, BigDecimal credits) {
        if (billingConfig.isOff() || !billingConfig.isStrict()) {
            return;
        }
        if (credits == null || credits.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }

        TenantBalance balance = tenantBalanceMapper.findByTenantId(tenantId);
        BigDecimal available = balance == null || balance.getBalance() == null
                ? BigDecimal.ZERO
                : balance.getBalance();
        if (available.compareTo(credits) < 0) {
            throwInsufficientBalance();
        }
    }

    @Override
    @Transactional
    @CacheEvict(value = "balance", key = "#tenantId")
    public void recharge(Long tenantId, BigDecimal amount, String refId) {
        ensureBalanceRow(tenantId);
        tenantBalanceMapper.recharge(tenantId, amount);

        BillingRecord record = new BillingRecord();
        record.setTenantId(tenantId);
        record.setOperation("recharge");
        record.setModality("RECHARGE");
        record.setCredits(amount);
        record.setBillingMode(billingMode());
        record.setRefId(refId);
        record.setSuccess(true);
        record.setUnitCount(1);
        billingRecordMapper.insert(record);

        log.info("Recharged: tenant={}, amount={}, ref={}", tenantId, amount, refId);
    }

    @Override
    @Transactional
    @CacheEvict(value = "balance", key = "#tenantId")
    public void rechargeByAdmin(Long tenantId, BigDecimal amount, Long adminId, String refId) {
        ensureBalanceRow(tenantId);
        tenantBalanceMapper.recharge(tenantId, amount);

        BillingRecord record = new BillingRecord();
        record.setTenantId(tenantId);
        record.setCreatedByAdminId(adminId);
        record.setOperation("recharge");
        record.setModality("RECHARGE");
        record.setCredits(amount);
        record.setBillingMode(billingMode());
        record.setRefId(refId);
        record.setSuccess(true);
        record.setUnitCount(1);
        billingRecordMapper.insert(record);

        log.info("Admin recharge: tenant={}, amount={}, admin={}, ref={}", tenantId, amount, adminId, refId);
    }

    @Override
    @Transactional
    @CacheEvict(value = "balance", key = "#tenantId")
    public void initBalance(Long tenantId, BigDecimal initialCredits) {
        if (tenantBalanceMapper.findByTenantId(tenantId) != null) return;

        TenantBalance b = new TenantBalance();
        b.setTenantId(tenantId);
        b.setBalance(initialCredits);
        b.setTotalRecharged(initialCredits);
        b.setTotalSpent(BigDecimal.ZERO);
        b.setVersion(0);
        tenantBalanceMapper.insert(b);

        BillingRecord record = new BillingRecord();
        record.setTenantId(tenantId);
        record.setOperation("recharge");
        record.setModality("RECHARGE");
        record.setCredits(initialCredits);
        record.setBillingMode(billingMode());
        record.setRefId("INITIAL");
        record.setSuccess(true);
        record.setUnitCount(1);
        billingRecordMapper.insert(record);

        log.info("Initialized balance: tenant={}, credits={}", tenantId, initialCredits);
    }

    @Override
    @Cacheable(value = "balance", key = "#tenantId")
    public TenantBalance getBalance(Long tenantId) {
        TenantBalance b = tenantBalanceMapper.findByTenantId(tenantId);
        if (b == null) {
            b = new TenantBalance();
            b.setTenantId(tenantId);
            b.setBalance(BigDecimal.ZERO);
            b.setTotalRecharged(BigDecimal.ZERO);
            b.setTotalSpent(BigDecimal.ZERO);
        }
        return b;
    }

    @Override
    public List<BillingRecord> getRecords(Long tenantId, int limit, long offset) {
        return billingRecordMapper.findByTenant(tenantId, limit, offset);
    }

    @Override
    public long countRecords(Long tenantId) {
        return billingRecordMapper.countByTenant(tenantId);
    }

    private String toJson(BillingChargeRequest request) {
        try {
            return objectMapper.writeValueAsString(request.getMeta());
        } catch (JsonProcessingException e) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, "计费元数据序列化失败: " + e.getMessage());
        }
    }

    private void ensureBalanceRow(Long tenantId) {
        if (tenantBalanceMapper.findByTenantId(tenantId) == null) {
            TenantBalance b = new TenantBalance();
            b.setTenantId(tenantId);
            b.setBalance(BigDecimal.ZERO);
            b.setTotalRecharged(BigDecimal.ZERO);
            b.setTotalSpent(BigDecimal.ZERO);
            b.setVersion(0);
            tenantBalanceMapper.insert(b);
        }
    }

    private boolean isSuccess(BillingChargeRequest request) {
        return request.getSuccess() == null || request.getSuccess();
    }

    private String billingMode() {
        String mode = billingConfig.getMode();
        if (mode == null || mode.isBlank()) {
            return "SOFT";
        }
        String normalized = mode.trim().toUpperCase(Locale.ROOT);
        return switch (normalized) {
            case "OFF", "SOFT", "STRICT" -> normalized;
            default -> billingConfig.isStrict() ? "STRICT" : "SOFT";
        };
    }

    private void throwInsufficientBalance() {
        throw new BizException(ErrorCode.INVALID_PARAM, "积分余额不足，请充值后重试");
    }
}
