package com.yaoai.admin.billing;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yaoai.admin.billing.dto.BillingRecordQuery;
import com.yaoai.admin.billing.dto.BillingCostItem;
import com.yaoai.admin.billing.dto.BillingSummaryItem;
import com.yaoai.admin.billing.dto.UsageDetailResponse;
import com.yaoai.admin.common.PageResult;
import com.yaoai.billing.service.BillingCostService;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.BillingRecord;
import com.yaoai.domain.entity.Project;
import com.yaoai.domain.entity.Tenant;
import com.yaoai.domain.entity.User;
import com.yaoai.domain.mapper.BillingRecordMapper;
import com.yaoai.domain.mapper.ProjectMapper;
import com.yaoai.domain.mapper.TenantMapper;
import com.yaoai.domain.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
@RequiredArgsConstructor
public class BillingAdminService {

    private final BillingRecordMapper billingRecordMapper;
    private final TenantMapper tenantMapper;
    private final UserMapper userMapper;
    private final ProjectMapper projectMapper;
    private final BillingCostService billingCostService;
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final Map<String, String> COST_LABELS = Map.ofEntries(
            Map.entry("outline_generate", "大纲生成"),
            Map.entry("episode_generate", "分集生成"),
            Map.entry("character_extract", "角色提取"),
            Map.entry("scene_extract", "场景提取"),
            Map.entry("storyboard_generate", "分镜生成"),
            Map.entry("storyboard_prompt_generate", "提示词生成"),
            Map.entry("image_generate", "图片生成"),
            Map.entry("character_image_generate", "角色图生成"),
            Map.entry("scene_image_generate", "场景图生成"),
            Map.entry("video_generate", "视频生成"),
            Map.entry("assembly", "视频合成")
    );

    public PageResult<Map<String, Object>> records(BillingRecordQuery query) {
        long page = Math.max(1, query.getPage());
        long size = Math.max(1, Math.min(query.getSize(), 100));
        long offset = (page - 1) * size;
        long total = billingRecordMapper.selectCount(buildQuery(query, null));
        List<BillingRecord> records = billingRecordMapper.selectList(buildQuery(query, null)
                .orderByDesc(BillingRecord::getCreatedAt)
                .last("LIMIT " + size + " OFFSET " + offset));
        return new PageResult<>(records.stream().map(this::toRecordRow).toList(), total, page, size);
    }

    public PageResult<Map<String, Object>> recharges(BillingRecordQuery query) {
        return recordsWithMode(query, true);
    }

    public PageResult<Map<String, Object>> consumes(BillingRecordQuery query) {
        return recordsWithMode(query, false);
    }

    public List<BillingSummaryItem> summary(String dim, BillingRecordQuery query) {
        String safeDim = StringUtils.hasText(dim) ? dim : "modality";
        List<BillingRecord> records = billingRecordMapper.selectList(buildQuery(query, null));
        return summarize(safeDim, records);
    }

    public UsageDetailResponse usageDetail(Long tenantId, LocalDateTime from, LocalDateTime to, String groupBy) {
        if (tenantMapper.selectById(tenantId) == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "租户不存在");
        }
        BillingRecordQuery query = new BillingRecordQuery();
        query.setTenantId(tenantId);
        query.setFrom(from);
        query.setTo(to);
        List<BillingRecord> records = billingRecordMapper.selectList(buildQuery(query, false));
        BigDecimal total = records.stream()
                .map(BillingRecord::getCredits)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return UsageDetailResponse.builder()
                .tenantId(tenantId)
                .groupBy(StringUtils.hasText(groupBy) ? groupBy : "model")
                .total(total)
                .breakdown(withPercent(summarize(StringUtils.hasText(groupBy) ? groupBy : "model", records), total))
                .build();
    }

    public List<BillingCostItem> costs() {
        return billingCostService.getCosts().entrySet().stream()
                .map(entry -> BillingCostItem.builder()
                        .operation(entry.getKey())
                        .label(COST_LABELS.getOrDefault(entry.getKey(), entry.getKey()))
                        .cost(entry.getValue())
                        .build())
                .sorted(Comparator.comparing(BillingCostItem::getOperation))
                .toList();
    }

    public List<BillingCostItem> saveCosts(Map<String, BigDecimal> costs, Long adminId) {
        billingCostService.saveCosts(costs, adminId);
        return costs();
    }

    private PageResult<Map<String, Object>> recordsWithMode(BillingRecordQuery query, boolean recharge) {
        long page = Math.max(1, query.getPage());
        long size = Math.max(1, Math.min(query.getSize(), 100));
        long offset = (page - 1) * size;
        long total = billingRecordMapper.selectCount(buildQuery(query, recharge));
        List<BillingRecord> records = billingRecordMapper.selectList(buildQuery(query, recharge)
                .orderByDesc(BillingRecord::getCreatedAt)
                .last("LIMIT " + size + " OFFSET " + offset));
        return new PageResult<>(records.stream().map(this::toRecordRow).toList(), total, page, size);
    }

    private LambdaQueryWrapper<BillingRecord> buildQuery(BillingRecordQuery query, Boolean recharge) {
        LambdaQueryWrapper<BillingRecord> wrapper = Wrappers.<BillingRecord>lambdaQuery();
        if (StringUtils.hasText(query.getModality())) {
            wrapper.eq(BillingRecord::getModality, query.getModality());
        }
        if (StringUtils.hasText(query.getModelId())) {
            wrapper.eq(BillingRecord::getModelId, query.getModelId());
        }
        if (StringUtils.hasText(query.getOperation())) {
            wrapper.eq(BillingRecord::getOperation, query.getOperation());
        }
        if (query.getSuccess() != null) {
            wrapper.eq(BillingRecord::getSuccess, query.getSuccess());
        }
        if (query.getTenantId() != null) {
            wrapper.eq(BillingRecord::getTenantId, query.getTenantId());
        }
        if (query.getUserId() != null) {
            wrapper.eq(BillingRecord::getUserId, query.getUserId());
        }
        if (query.getFrom() != null) {
            wrapper.ge(BillingRecord::getCreatedAt, query.getFrom());
        }
        if (query.getTo() != null) {
            wrapper.le(BillingRecord::getCreatedAt, query.getTo());
        }
        if (recharge != null) {
            if (recharge) {
                wrapper.and(w -> w.eq(BillingRecord::getModality, "RECHARGE")
                        .or()
                        .eq(BillingRecord::getOperation, "recharge"));
            } else {
                wrapper.and(w -> w.ne(BillingRecord::getModality, "RECHARGE")
                        .or()
                        .isNull(BillingRecord::getModality))
                        .ne(BillingRecord::getOperation, "recharge");
            }
        }
        return wrapper;
    }

    private Map<String, Object> toRecordRow(BillingRecord record) {
        Map<String, Object> row = new LinkedHashMap<>();
        row.put("id", record.getId());
        row.put("createdAt", record.getCreatedAt() == null ? "" : DATE_TIME_FORMATTER.format(record.getCreatedAt()));
        row.put("tenantId", record.getTenantId());
        row.put("tenantName", tenantName(record.getTenantId()));
        row.put("projectId", record.getProjectId());
        row.put("projectName", projectName(record.getProjectId()));
        row.put("userId", record.getUserId());
        row.put("userName", userName(record.getUserId()));
        row.put("createdByAdminId", record.getCreatedByAdminId());
        row.put("operation", record.getOperation());
        row.put("operationLabel", operationLabel(record.getOperation()));
        row.put("modality", record.getModality());
        row.put("modelProvider", record.getModelProvider());
        row.put("modelId", record.getModelId());
        row.put("modelName", StringUtils.hasText(record.getModelName()) ? record.getModelName() : record.getModelId());
        row.put("unitCount", record.getUnitCount());
        row.put("success", record.getSuccess());
        row.put("successLabel", Boolean.FALSE.equals(record.getSuccess()) ? "失败" : "成功");
        row.put("credits", record.getCredits());
        row.put("billingMode", record.getBillingMode());
        row.put("refId", record.getRefId());
        row.put("billingMeta", record.getBillingMeta());
        return row;
    }

    private String operationLabel(String operation) {
        if (!StringUtils.hasText(operation)) {
            return "-";
        }
        if ("recharge".equals(operation)) {
            return "充值入账";
        }
        return COST_LABELS.getOrDefault(operation, operation);
    }

    private List<BillingSummaryItem> summarize(String dim, List<BillingRecord> records) {
        Map<String, List<BillingRecord>> grouped = records.stream()
                .collect(LinkedHashMap::new,
                        (map, record) -> map.computeIfAbsent(summaryKey(dim, record), k -> new java.util.ArrayList<>()).add(record),
                        Map::putAll);
        return grouped.entrySet().stream()
                .map(entry -> toSummaryItem(dim, entry.getKey(), entry.getValue()))
                .sorted(Comparator.comparing(BillingSummaryItem::getTotalCredits).reversed())
                .toList();
    }

    private BillingSummaryItem toSummaryItem(String dim, String key, List<BillingRecord> records) {
        BigDecimal credits = records.stream()
                .map(BillingRecord::getCredits)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        long units = records.stream()
                .map(BillingRecord::getUnitCount)
                .filter(Objects::nonNull)
                .mapToLong(Integer::longValue)
                .sum();
        long success = records.stream().filter(r -> !Boolean.FALSE.equals(r.getSuccess())).count();
        long fail = records.size() - success;
        return BillingSummaryItem.builder()
                .key(key)
                .label(summaryLabel(dim, key, records.get(0)))
                .totalCredits(credits)
                .totalUnits(units)
                .count(records.size())
                .successCount(success)
                .failCount(fail)
                .percent(BigDecimal.ZERO)
                .build();
    }

    private List<BillingSummaryItem> withPercent(List<BillingSummaryItem> items, BigDecimal total) {
        if (total == null || total.compareTo(BigDecimal.ZERO) <= 0) {
            return items;
        }
        for (BillingSummaryItem item : items) {
            item.setPercent(item.getTotalCredits()
                    .multiply(new BigDecimal("100"))
                    .divide(total, 2, RoundingMode.HALF_UP));
        }
        return items;
    }

    private String summaryKey(String dim, BillingRecord record) {
        return switch (dim) {
            case "model" -> blank(record.getModelProvider()) + "|" + blank(record.getModelId());
            case "tenant" -> String.valueOf(record.getTenantId());
            case "user" -> String.valueOf(record.getUserId());
            case "day" -> record.getCreatedAt() == null ? "unknown" : record.getCreatedAt().toLocalDate().toString();
            case "project" -> String.valueOf(record.getProjectId());
            case "modality" -> blank(record.getModality());
            default -> throw new BizException(ErrorCode.INVALID_PARAM, "不支持的汇总维度: " + dim);
        };
    }

    private String summaryLabel(String dim, String key, BillingRecord sample) {
        return switch (dim) {
            case "model" -> StringUtils.hasText(sample.getModelName()) ? sample.getModelName() : key;
            case "tenant" -> tenantName(sample.getTenantId());
            case "user" -> userName(sample.getUserId());
            case "project" -> projectName(sample.getProjectId());
            default -> key;
        };
    }

    private String tenantName(Long tenantId) {
        Tenant tenant = tenantId == null ? null : tenantMapper.selectById(tenantId);
        return tenant == null ? String.valueOf(tenantId) : tenant.getName();
    }

    private String userName(Long userId) {
        User user = userId == null ? null : userMapper.selectById(userId);
        if (user == null) {
            return String.valueOf(userId);
        }
        return StringUtils.hasText(user.getEmail()) ? user.getEmail() : user.getUsername();
    }

    private String projectName(Long projectId) {
        Project project = projectId == null ? null : projectMapper.selectById(projectId);
        return project == null ? String.valueOf(projectId) : project.getName();
    }

    private String blank(String value) {
        return StringUtils.hasText(value) ? value : "unknown";
    }
}
