package com.yaoai.api.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoai.api.dto.script.ParsedScript;
import com.yaoai.api.dto.script.ScriptImportResultDTO;
import com.yaoai.api.service.ScriptImportService;
import com.yaoai.api.util.DocScriptParser;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.*;
import com.yaoai.domain.mapper.*;
import com.yaoai.storage.service.TosService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class ScriptImportServiceImpl implements ScriptImportService {

    private final ProjectAssetMapper assetMapper;
    private final TosService tosService;
    private final OutlineMapper outlineMapper;
    private final EpisodeMapper episodeMapper;
    private final StoryboardMapper storyboardMapper;
    private final CharacterMapper characterMapper;
    private final SceneMapper sceneMapper;
    private final ObjectMapper objectMapper;

    // ── 公开方法 ──────────────────────────────────────────────────────────────

    @Override
    public ScriptImportResultDTO parseScript(Long projectId, Long tenantId) {
        ProjectAsset asset = findScriptAsset(projectId, tenantId);
        ParsedScript parsed = parseFromTos(asset.getTosKey());
        return toSummaryDTO(parsed);
    }

    @Override
    @Transactional
    public ScriptImportResultDTO importScript(Long projectId, Long tenantId) {
        ProjectAsset asset = findScriptAsset(projectId, tenantId);
        ParsedScript parsed = parseFromTos(asset.getTosKey());

        // 1. 清空该项目的旧数据
        deleteExisting(projectId, tenantId);

        // 2. 创建大纲
        Outline outline = new Outline();
        outline.setProjectId(projectId);
        outline.setTenantId(tenantId);
        outline.setTitle(parsed.getTitle());
        outline.setGenre(parsed.getGenre());
        outline.setSynopsis(truncate(parsed.getSynopsis(), 1000));
        outline.setEpisodeCount(parsed.getEpisodeCount());
        outline.setStatus("ready");
        outlineMapper.insert(outline);

        // 3. 创建角色，同时建立姓名→ID映射（供分镜头关联）
        Map<String, Long> charIdMap = new LinkedHashMap<>();
        for (ParsedScript.Character pc : parsed.getCharacters()) {
            com.yaoai.domain.entity.Character ch = new com.yaoai.domain.entity.Character();
            ch.setProjectId(projectId);
            ch.setTenantId(tenantId);
            ch.setName(pc.getName());
            ch.setPersonality(pc.getDescription());
            ch.setRoleType(pc.getRoleType());
            ch.setGender(pc.getGender());
            ch.setStatus("draft");
            characterMapper.insert(ch);
            charIdMap.put(pc.getName(), ch.getId());
        }

        // 4. 创建场景
        for (ParsedScript.Scene ps : parsed.getScenes()) {
            Scene scene = new Scene();
            scene.setProjectId(projectId);
            scene.setTenantId(tenantId);
            scene.setName(ps.getName());
            scene.setSceneType(ps.getSceneType());
            scene.setDescription(ps.getDescription());
            scene.setStatus("draft");
            sceneMapper.insert(scene);
        }

        // 5. 建立集数→大纲/脚本索引
        Map<Integer, ParsedScript.EpisodeOutline> outlineByEp = parsed.getEpisodeOutlines().stream()
                .collect(Collectors.toMap(ParsedScript.EpisodeOutline::getEpisodeNumber, o -> o, (a, b) -> a));
        Map<Integer, ParsedScript.EpisodeScript> scriptByEp = parsed.getEpisodeScripts().stream()
                .collect(Collectors.toMap(ParsedScript.EpisodeScript::getEpisodeNumber, s -> s, (a, b) -> a));

        int storyboardCount = 0;
        int epCount = parsed.getEpisodeCount();

        // 6. 逐集创建 Episode + Storyboard
        for (int epNum = 1; epNum <= epCount; epNum++) {
            ParsedScript.EpisodeOutline epOutline = outlineByEp.get(epNum);
            ParsedScript.EpisodeScript epScript = scriptByEp.get(epNum);

            Episode episode = new Episode();
            episode.setProjectId(projectId);
            episode.setTenantId(tenantId);
            episode.setOutlineId(outline.getId());
            episode.setEpisodeNumber(epNum);
            episode.setTitle("第" + epNum + "集");
            episode.setSummary(epOutline != null ? truncate(epOutline.getContent(), 500) : "");
            episode.setScript(buildScriptText(epScript));
            episode.setStatus("ready");
            episodeMapper.insert(episode);

            if (epScript != null) {
                int seqNum = 1;
                for (ParsedScript.SceneSection section : epScript.getSceneSections()) {
                    for (ParsedScript.Shot shot : section.getShots()) {
                        Storyboard sb = new Storyboard();
                        sb.setProjectId(projectId);
                        sb.setTenantId(tenantId);
                        sb.setEpisodeId(episode.getId());
                        sb.setSequenceNum(seqNum++);
                        sb.setSceneNumber(section.getSectionId());
                        String desc = shot.getDescription();
                        sb.setShortDescription(truncate(desc, 60));
                        sb.setDetailedDescription(desc);
                        sb.setCharacters(buildCharactersJson(section.getCharacterNames(), charIdMap));
                        sb.setDurationSeconds(5);
                        sb.setStatus("draft");
                        storyboardMapper.insert(sb);
                        storyboardCount++;
                    }
                }
            }
        }

        ScriptImportResultDTO dto = toSummaryDTO(parsed);
        dto.setStoryboardCount(storyboardCount);
        return dto;
    }

    // ── 私有辅助 ──────────────────────────────────────────────────────────────

    private ProjectAsset findScriptAsset(Long projectId, Long tenantId) {
        return assetMapper.findByProject(projectId).stream()
                .filter(a -> tenantId.equals(a.getTenantId()) && "script".equals(a.getAssetType()))
                .findFirst()
                .orElseThrow(() -> new BizException(ErrorCode.NOT_FOUND, "未找到上传的剧本文件"));
    }

    private ParsedScript parseFromTos(String tosKey) {
        try (InputStream in = tosService.getInputStream(tosKey)) {
            return DocScriptParser.parse(in);
        } catch (IOException e) {
            log.error("Failed to parse script docx: key={}", tosKey, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "解析剧本文档失败: " + e.getMessage());
        }
    }

    private void deleteExisting(Long projectId, Long tenantId) {
        storyboardMapper.delete(Wrappers.<Storyboard>lambdaQuery()
                .eq(Storyboard::getProjectId, projectId).eq(Storyboard::getTenantId, tenantId));
        episodeMapper.delete(Wrappers.<Episode>lambdaQuery()
                .eq(Episode::getProjectId, projectId).eq(Episode::getTenantId, tenantId));
        outlineMapper.delete(Wrappers.<Outline>lambdaQuery()
                .eq(Outline::getProjectId, projectId).eq(Outline::getTenantId, tenantId));
        characterMapper.delete(Wrappers.<com.yaoai.domain.entity.Character>lambdaQuery()
                .eq(com.yaoai.domain.entity.Character::getProjectId, projectId).eq(com.yaoai.domain.entity.Character::getTenantId, tenantId));
        sceneMapper.delete(Wrappers.<Scene>lambdaQuery()
                .eq(Scene::getProjectId, projectId).eq(Scene::getTenantId, tenantId));
    }

    private ScriptImportResultDTO toSummaryDTO(ParsedScript parsed) {
        int sbCount = parsed.getEpisodeScripts().stream()
                .flatMap(es -> es.getSceneSections().stream())
                .mapToInt(s -> s.getShots().size())
                .sum();
        ScriptImportResultDTO dto = new ScriptImportResultDTO();
        dto.setTitle(parsed.getTitle());
        dto.setGenre(parsed.getGenre());
        dto.setSynopsis(truncate(parsed.getSynopsis(), 200));
        dto.setEpisodeCount(parsed.getEpisodeCount());
        dto.setCharacterCount(parsed.getCharacters().size());
        dto.setSceneCount(parsed.getScenes().size());
        dto.setStoryboardCount(sbCount);
        return dto;
    }

    /** 将 EpisodeScript 序列化为可读脚本文本，存入 episode.script 字段 */
    private String buildScriptText(ParsedScript.EpisodeScript epScript) {
        if (epScript == null) return "";
        StringBuilder sb = new StringBuilder();
        if (epScript.getLocation() != null) {
            sb.append("场地：").append(epScript.getLocation()).append("\n");
        }
        for (ParsedScript.SceneSection section : epScript.getSceneSections()) {
            sb.append("\n[").append(section.getSectionId()).append("]");
            if (section.getCharacterNames() != null && !section.getCharacterNames().isEmpty()) {
                sb.append("（").append(section.getCharacterNames()).append("）");
            }
            sb.append("\n");
            for (ParsedScript.Shot shot : section.getShots()) {
                sb.append("镜头").append(shot.getShotNumber()).append("：")
                  .append(shot.getDescription()).append("\n");
            }
        }
        return sb.toString().trim();
    }

    /**
     * 将角色名列表序列化为 JSON 格式：[{"id":"xxx","name":"乔月"},...]
     * 通过 charIdMap 尝试关联已导入的角色 ID。
     */
    private String buildCharactersJson(String characterNames, Map<String, Long> charIdMap) {
        if (characterNames == null || characterNames.isEmpty()) return "[]";
        try {
            List<Map<String, Object>> list = Arrays.stream(characterNames.split("[，,、]"))
                    .map(String::trim).filter(n -> !n.isEmpty())
                    .map(name -> {
                        Map<String, Object> m = new LinkedHashMap<>();
                        m.put("id", String.valueOf(charIdMap.getOrDefault(name, 0L)));
                        m.put("name", name);
                        return m;
                    })
                    .collect(Collectors.toList());
            return objectMapper.writeValueAsString(list);
        } catch (Exception e) {
            return "[]";
        }
    }

    private static String truncate(String s, int maxLen) {
        if (s == null) return "";
        return s.length() > maxLen ? s.substring(0, maxLen) : s;
    }
}
