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.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 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<BillingRecord> 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, total, page, size);
    }

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

    public PageResult<BillingRecord> 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<BillingRecord> 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, 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 (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 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";
    }
}
