package com.yaoai.pipeline.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoai.ai.core.model.ChatMessage;
import com.yaoai.ai.core.model.ChatRequest;
import com.yaoai.ai.core.service.LlmService;
import com.yaoai.billing.dto.BillingChargeRequest;
import com.yaoai.billing.service.BillingService;
import com.yaoai.common.context.UserContext;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.Episode;
import com.yaoai.domain.entity.Outline;
import com.yaoai.domain.entity.ProjectConsistencyBible;
import com.yaoai.domain.entity.ScriptUnderstanding;
import com.yaoai.domain.mapper.EpisodeMapper;
import com.yaoai.domain.mapper.OutlineMapper;
import com.yaoai.domain.mapper.ProjectConsistencyBibleMapper;
import com.yaoai.domain.mapper.ScriptUnderstandingMapper;
import com.yaoai.pipeline.service.OutlinePipelineService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class OutlinePipelineServiceImpl implements OutlinePipelineService {

    private static final int MAX_CONTEXT_CHARS = 12000;

    private final LlmService llmService;
    private final OutlineMapper outlineMapper;
    private final EpisodeMapper episodeMapper;
    private final ScriptUnderstandingMapper scriptUnderstandingMapper;
    private final ProjectConsistencyBibleMapper projectConsistencyBibleMapper;
    private final ObjectMapper objectMapper;
    private final BillingService billingService;

    private static final String OUTLINE_SYSTEM = """
            你是一位专业的影视编剧助手。根据用户提供的剧本内容，提取并生成结构化大纲。
            如果输入包含“剧情理解事实”或“一致性圣经”，必须优先遵循其中的事实与证据，不得自由改写主线。
            请以 JSON 格式返回，格式如下：
            {
              "title": "剧集名称",
              "genre": "类型（如：都市爱情/古装武侠/科幻悬疑等）",
              "episode_count": 集数（整数），
              "synopsis": "200字以内的整体故事梗概"
            }
            只返回 JSON，不要有其他说明文字。
            """;

    private static final String EPISODE_SYSTEM = """
            你是一位专业的影视编剧助手。根据用户提供的大纲信息，生成每集的详细内容。
            如果输入包含“剧情理解事实”或“一致性圣经”，必须按事实展开分集，不得新增与事实冲突的人物关系、地点和动机。
            请以 JSON 数组格式返回，每集格式如下：
            [
              {
                "episode_number": 集数（整数，从1开始）,
                "title": "本集标题",
                "summary": "本集150字以内的剧情摘要",
                "script": "本集关键场景的简要剧本（300字以内）"
              }
            ]
            只返回 JSON 数组，不要有其他说明文字。
            """;

    @Override
    public Outline generateOutline(Long projectId, Long tenantId, String scriptContent) {
        log.info("Generating outline: projectId={}", projectId);
        String enrichedScriptContent = enrichWithContinuityContext(projectId, tenantId, scriptContent);

        ChatRequest request = ChatRequest.builder()
                .messages(List.of(
                        ChatMessage.system(OUTLINE_SYSTEM),
                        ChatMessage.user("剧本内容如下：\n\n" + enrichedScriptContent)
                ))
                .build();

        billingService.checkBalance(BillingChargeRequest.builder()
                .tenantId(tenantId)
                .userId(UserContext.require())
                .projectId(projectId)
                .operation("outline_generate")
                .modality("TEXT")
                .modelProvider(llmService.getProvider())
                .modelId(llmService.getModelId())
                .modelName(llmService.getModelName())
                .unitCount(1)
                .refId(String.valueOf(projectId))
                .build());

        String raw = llmService.chat(request);
        log.debug("Outline LLM response: {}", raw);

        try {
            String json = extractJson(raw);
            Map<String, Object> data = objectMapper.readValue(json, new TypeReference<>() {});

            Outline outline = new Outline();
            outline.setProjectId(projectId);
            outline.setTenantId(tenantId);
            outline.setTitle((String) data.getOrDefault("title", "未命名"));
            outline.setGenre((String) data.getOrDefault("genre", ""));
            outline.setSynopsis((String) data.getOrDefault("synopsis", ""));
            outline.setRawJson(json);
            outline.setStatus("ready");

            Object epCount = data.get("episode_count");
            outline.setEpisodeCount(epCount instanceof Number ? ((Number) epCount).intValue() : 1);

            outlineMapper.insert(outline);
            billingService.charge(BillingChargeRequest.builder()
                    .tenantId(tenantId)
                    .userId(UserContext.require())
                    .projectId(projectId)
                    .operation("outline_generate")
                    .modality("TEXT")
                    .modelProvider(llmService.getProvider())
                    .modelId(llmService.getModelId())
                    .modelName(llmService.getModelName())
                    .unitCount(1)
                    .refId(String.valueOf(projectId))
                    .build());
            log.info("Outline saved: id={}, title={}", outline.getId(), outline.getTitle());
            return outline;
        } catch (Exception e) {
            log.error("Outline parsing failed, raw={}", raw, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "大纲解析失败: " + e.getMessage());
        }
    }

    @Override
    public List<Episode> generateEpisodes(Long outlineId, Long projectId, Long tenantId) {
        Outline outline = outlineMapper.selectById(outlineId);
        if (outline == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "大纲不存在");
        }

        log.info("Generating episodes: outlineId={}, count={}", outlineId, outline.getEpisodeCount());

        String userMsg = String.format(
                "%s\n\n剧集名称：%s\n类型：%s\n集数：%d\n故事梗概：%s\n\n请生成每集详细内容。",
                buildContinuityContext(projectId, tenantId),
                outline.getTitle(), outline.getGenre(), outline.getEpisodeCount(), outline.getSynopsis()
        );

        ChatRequest request = ChatRequest.builder()
                .messages(List.of(
                        ChatMessage.system(EPISODE_SYSTEM),
                        ChatMessage.user(userMsg)
                ))
                .build();

        billingService.checkBalance(BillingChargeRequest.builder()
                .tenantId(tenantId)
                .userId(UserContext.require())
                .projectId(projectId)
                .operation("episode_generate")
                .modality("TEXT")
                .modelProvider(llmService.getProvider())
                .modelId(llmService.getModelId())
                .modelName(llmService.getModelName())
                .unitCount(1)
                .refId(String.valueOf(outlineId))
                .build());

        String raw = llmService.chat(request);
        log.debug("Episode LLM response: {}", raw);

        try {
            String json = extractJson(raw);
            List<Map<String, Object>> items = objectMapper.readValue(json, new TypeReference<>() {});

            List<Episode> episodes = new ArrayList<>();
            for (Map<String, Object> item : items) {
                Episode ep = new Episode();
                ep.setOutlineId(outlineId);
                ep.setProjectId(projectId);
                ep.setTenantId(tenantId);
                ep.setEpisodeNumber(((Number) item.getOrDefault("episode_number", 1)).intValue());
                ep.setTitle((String) item.getOrDefault("title", ""));
                ep.setSummary((String) item.getOrDefault("summary", ""));
                ep.setScript((String) item.getOrDefault("script", ""));
                ep.setStatus("ready");
                episodeMapper.insert(ep);
                episodes.add(ep);
            }
            billingService.charge(BillingChargeRequest.builder()
                    .tenantId(tenantId)
                    .userId(UserContext.require())
                    .projectId(projectId)
                    .operation("episode_generate")
                    .modality("TEXT")
                    .modelProvider(llmService.getProvider())
                    .modelId(llmService.getModelId())
                    .modelName(llmService.getModelName())
                    .unitCount(1)
                    .refId(String.valueOf(outlineId))
                    .build());
            log.info("Episodes saved: count={}", episodes.size());
            return episodes;
        } catch (Exception e) {
            log.error("Episode parsing failed, raw={}", raw, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "分集解析失败: " + e.getMessage());
        }
    }

    @Override
    public Outline getOutline(Long projectId, Long tenantId) {
        return outlineMapper.findLatestByProject(projectId, tenantId);
    }

    @Override
    public Outline updateOutline(Long projectId, Long tenantId, Outline req) {
        Outline existing = outlineMapper.findLatestByProject(projectId, tenantId);
        if (existing == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "大纲不存在，请先生成或保存大纲");
        }
        if (req.getTitle() != null) existing.setTitle(req.getTitle());
        if (req.getGenre() != null) existing.setGenre(req.getGenre());
        if (req.getSynopsis() != null) existing.setSynopsis(req.getSynopsis());
        if (req.getEpisodeCount() != null && req.getEpisodeCount() > 0) {
            existing.setEpisodeCount(req.getEpisodeCount());
        }
        existing.setStatus("ready");
        outlineMapper.updateById(existing);
        log.info("Outline updated: id={}, projectId={}", existing.getId(), projectId);
        return existing;
    }

    @Override
    public List<Episode> getEpisodes(Long projectId, Long tenantId) {
        return episodeMapper.findByProject(projectId, tenantId);
    }

    @Override
    public Episode createEpisode(Long projectId, Long tenantId, Episode req) {
        Outline outline = outlineMapper.findLatestByProject(projectId, tenantId);
        if (outline == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "请先生成或保存大纲");
        }
        Integer epNum = req.getEpisodeNumber();
        if (epNum == null || epNum <= 0) {
            int max = episodeMapper.findByProject(projectId, tenantId).stream()
                    .mapToInt(e -> e.getEpisodeNumber() == null ? 0 : e.getEpisodeNumber())
                    .max().orElse(0);
            epNum = max + 1;
        }
        Episode ep = new Episode();
        ep.setOutlineId(outline.getId());
        ep.setProjectId(projectId);
        ep.setTenantId(tenantId);
        ep.setEpisodeNumber(epNum);
        ep.setTitle(req.getTitle() == null ? "" : req.getTitle());
        ep.setSummary(req.getSummary() == null ? "" : req.getSummary());
        ep.setScript(req.getScript() == null ? "" : req.getScript());
        ep.setStatus("ready");
        episodeMapper.insert(ep);
        log.info("Episode manually created: id={}, episodeNumber={}", ep.getId(), epNum);
        return ep;
    }

    @Override
    public Episode updateEpisode(Long projectId, Long tenantId, Long episodeId, Episode req) {
        Episode existing = episodeMapper.selectById(episodeId);
        if (existing == null
                || !tenantId.equals(existing.getTenantId())
                || !projectId.equals(existing.getProjectId())) {
            throw new BizException(ErrorCode.NOT_FOUND, "分集不存在");
        }
        if (req.getEpisodeNumber() != null && req.getEpisodeNumber() > 0) {
            existing.setEpisodeNumber(req.getEpisodeNumber());
        }
        if (req.getTitle() != null) existing.setTitle(req.getTitle());
        if (req.getSummary() != null) existing.setSummary(req.getSummary());
        if (req.getScript() != null) existing.setScript(req.getScript());
        episodeMapper.updateById(existing);
        log.info("Episode updated: id={}", episodeId);
        return existing;
    }

    private String extractJson(String raw) {
        // strip markdown code fences if present
        String s = raw.strip();
        if (s.startsWith("```")) {
            int start = s.indexOf('\n') + 1;
            int end = s.lastIndexOf("```");
            if (end > start) s = s.substring(start, end).strip();
        }
        return s;
    }

    private String enrichWithContinuityContext(Long projectId, Long tenantId, String content) {
        String continuity = buildContinuityContext(projectId, tenantId);
        String base = content == null ? "" : content;
        if (continuity.isBlank()) {
            return base;
        }
        return continuity + "\n\n用户输入/剧本内容：\n" + base;
    }

    private String buildContinuityContext(Long projectId, Long tenantId) {
        StringBuilder builder = new StringBuilder();
        ScriptUnderstanding understanding = scriptUnderstandingMapper.findLatestByProject(projectId, tenantId);
        if (understanding != null && understanding.getUnderstandingJson() != null && !understanding.getUnderstandingJson().isBlank()) {
            builder.append("剧情理解事实（最高优先级，必须遵循）：\n")
                    .append(limit(understanding.getUnderstandingJson(), MAX_CONTEXT_CHARS))
                    .append("\n\n");
        }
        ProjectConsistencyBible bible = projectConsistencyBibleMapper.findByProject(projectId, tenantId);
        if (bible != null && bible.getBibleJson() != null && !bible.getBibleJson().isBlank()) {
            builder.append("一致性圣经（角色/场景/剧情锁定项）：\n")
                    .append(limit(bible.getBibleJson(), MAX_CONTEXT_CHARS))
                    .append("\n\n");
        }
        return builder.toString().trim();
    }

    private String limit(String value, int maxChars) {
        if (value == null || value.length() <= maxChars) {
            return value == null ? "" : value;
        }
        return value.substring(0, maxChars) + "\n[上下文过长，已截断]";
    }
}
