package com.ruoyi.api;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.ai.domain.AiOrder;
import com.ruoyi.ai.domain.ByteBodyReq;
import com.ruoyi.ai.domain.ByteBodyRes;
import com.ruoyi.ai.domain.ContentItem;
import com.ruoyi.ai.domain.ImageUrl;
import com.ruoyi.ai.domain.content;
import com.ruoyi.ai.service.IAiOrderService;
import com.ruoyi.ai.service.IByteDeptApiKeyService;
import com.ruoyi.ai.service.IByteService;
import com.ruoyi.api.request.PortalVideoGenRequest;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TencentCosUtil;
import com.ruoyi.config.PortalVideoProperties;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 门户视频生成：按用户二级部门 byte_api_key 调用火山；任务列表含库表与火山过滤列表。
 */
@Api(tags = "门户-视频生成")
@RestController
@RequestMapping("/api/portal/video")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class PortalVideoController extends BaseController {

    private final IByteService byteService;
    private final IByteDeptApiKeyService byteDeptApiKeyService;
    private final IAiOrderService aiOrderService;
    private final TencentCosUtil tencentCosUtil;
    private final PortalVideoProperties portalVideoProperties;

    @Value("${volcengine.ark.callbackUrl:}")
    private String volcCallbackUrl;

    private static final ObjectMapper OM = new ObjectMapper();

    private String apiKey() {
        return byteDeptApiKeyService.resolveVolcApiKey(SecurityUtils.getAiUserId());
    }

    /** 与 ai_manager.type、portal.video.function-type 对齐，用于扣费 */
    private String resolveFunctionType(PortalVideoGenRequest req) {
        if (StringUtils.isNotEmpty(req.getFunctionType())) {
            return req.getFunctionType();
        }
        if (StringUtils.isNotEmpty(portalVideoProperties.getFunctionType())) {
            return portalVideoProperties.getFunctionType();
        }
        return "21";
    }

    private void applyOptionalParams(ByteBodyReq body, PortalVideoGenRequest req) {
        PortalVideoProperties.Defaults d = portalVideoProperties.getDefaults();
        body.setDuration(req.getDuration() != null ? req.getDuration() : d.getDuration());
        body.setResolution(StringUtils.isNotEmpty(req.getResolution()) ? req.getResolution() : d.getResolution());
        body.setRatio(StringUtils.isNotEmpty(req.getRatio()) ? req.getRatio() : d.getRatio());
        if (StringUtils.isNotEmpty(volcCallbackUrl)) {
            body.setCallback_url(volcCallbackUrl);
        }
    }

    private ByteBodyReq newVideoBody(PortalVideoGenRequest req, List<ContentItem> content) {
        String modelId = StringUtils.isNotEmpty(req.getModel()) ? req.getModel() : portalVideoProperties.resolveDefaultModelId();
        if (StringUtils.isEmpty(modelId)) {
            throw new ServiceException("未配置门户视频模型，请在 application.yml 的 portal.video 中配置 models 与 defaults.model");
        }
        ByteBodyReq body = new ByteBodyReq();
        body.setModel(modelId);
        body.setContent(content);
        applyOptionalParams(body, req);
        return body;
    }

    private void applyOrderImages(AiOrder aiOrder, PortalVideoGenRequest req) {
        if (StringUtils.isNotEmpty(req.getFirstUrl())) {
            aiOrder.setImg1(req.getFirstUrl());
        }
        if (StringUtils.isNotEmpty(req.getLastUrl())) {
            aiOrder.setImg2(req.getLastUrl());
        }
        if (StringUtils.isNotEmpty(req.getReferenceUrl())) {
            aiOrder.setImg1(req.getReferenceUrl());
        }
    }

    /**
     * 写入订单：提示词、生成模式、图床 URL，以及模型/时长/分辨率/比例与完整 JSON 参数（便于对账与审计）。
     */
    private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body, String functionTypeResolved) {
        aiOrder.setText(req.getText());
        aiOrder.setMode(mode);
        applyOrderImages(aiOrder, req);
        if (req.getDuration() != null) {
            aiOrder.setDuration(req.getDuration());
        } else if (body.getDuration() != null) {
            aiOrder.setDuration(body.getDuration());
        }
        if (StringUtils.isNotEmpty(body.getResolution())) {
            aiOrder.setResolution(body.getResolution());
        }
        if (StringUtils.isNotEmpty(body.getRatio())) {
            aiOrder.setRatio(body.getRatio());
        }
        if (StringUtils.isNotEmpty(body.getModel())) {
            aiOrder.setModel(body.getModel());
        }
        try {
            Map<String, Object> snap = new LinkedHashMap<>();
            snap.put("generationMode", mode);
            snap.put("prompt", req.getText());
            snap.put("functionType", functionTypeResolved);
            snap.put("model", body.getModel());
            snap.put("duration", body.getDuration());
            snap.put("resolution", body.getResolution());
            snap.put("ratio", body.getRatio());
            if (StringUtils.isNotEmpty(req.getFirstUrl())) {
                snap.put("firstUrl", req.getFirstUrl());
            }
            if (StringUtils.isNotEmpty(req.getLastUrl())) {
                snap.put("lastUrl", req.getLastUrl());
            }
            if (StringUtils.isNotEmpty(req.getReferenceUrl())) {
                snap.put("referenceUrl", req.getReferenceUrl());
            }
            if (req.getContent() != null && !req.getContent().isEmpty()) {
                snap.put("content", req.getContent());
            }
            aiOrder.setVideoParams(OM.writeValueAsString(snap));
        } catch (Exception e) {
            aiOrder.setVideoParams("{\"error\":\"video_params_serialize_failed\"}");
        }
    }

    /** 写入 video_params.volcTaskId，任务成功后 result 会改为成品 URL，仍应用此 id 校验归属与轮询 */
    private void mergeVolcTaskIdIntoVideoParams(AiOrder aiOrder, String volcTaskId) {
        if (StringUtils.isEmpty(volcTaskId)) {
            return;
        }
        try {
            ObjectNode node;
            if (StringUtils.isNotEmpty(aiOrder.getVideoParams())) {
                JsonNode existing = OM.readTree(aiOrder.getVideoParams());
                if (existing instanceof ObjectNode) {
                    node = (ObjectNode) existing;
                } else {
                    node = OM.createObjectNode();
                    node.set("snapshotBeforeVolcId", existing);
                }
            } else {
                node = OM.createObjectNode();
            }
            node.put("volcTaskId", volcTaskId);
            aiOrder.setVideoParams(OM.writeValueAsString(node));
        } catch (Exception e) {
            aiOrder.setVideoParams("{\"volcTaskId\":\"" + volcTaskId.replace("\"", "") + "\"}");
        }
    }

    private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) {
        String functionType = resolveFunctionType(req);
        AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
        if (aiOrder == null) {
            return AjaxResult.error(-1, "You have a low balance, please recharge");
        }
        try {
            fillVideoOrderRecord(aiOrder, req, mode, byteBodyReq, functionType);
            aiOrderService.updateAiOrder(aiOrder);

            String key = apiKey();
            ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
            String id = byteBodyRes.getId();
            if (id == null) {
                aiOrderService.orderFailure(aiOrder);
                return AjaxResult.error(-2, "generation failed, balance has been refunded");
            }
            mergeVolcTaskIdIntoVideoParams(aiOrder, id);
            aiOrder.setResult(id);
            aiOrderService.orderSuccess(aiOrder);
            return AjaxResult.success(byteBodyRes);
        } catch (Exception e) {
            aiOrderService.orderFailure(aiOrder);
            throw new RuntimeException(e);
        }
    }

    @PostMapping("/text-to-video")
    @ApiOperation("文生视频")
    public AjaxResult textToVideo(@RequestBody PortalVideoGenRequest request) {
        List<ContentItem> contentList;
        if (request.getContent() != null && !request.getContent().isEmpty()) {
            contentList = request.getContent();
        } else {
            if (StringUtils.isEmpty(request.getText())) {
                return AjaxResult.error("请输入视频描述文本");
            }
            contentList = new ArrayList<>();
            ContentItem textItem = new ContentItem();
            textItem.setType("text");
            textItem.setText(request.getText());
            contentList.add(textItem);
        }

        ByteBodyReq body = newVideoBody(request, contentList);
        return submitOrderAndCreate(request, "text-to-video", body);
    }

    @PostMapping("/image-first-frame")
    @ApiOperation("图生视频-基于首帧")
    public AjaxResult imageFirstFrame(@RequestBody PortalVideoGenRequest request) {
        if (StringUtils.isEmpty(request.getFirstUrl())) {
            return AjaxResult.error("请上传首帧图片");
        }
        if (StringUtils.isEmpty(request.getText())) {
            return AjaxResult.error("请输入视频描述文本");
        }
        List<ContentItem> contentList = buildTextAndFirstFrame(request.getText(), request.getFirstUrl());
        ByteBodyReq body = newVideoBody(request, contentList);
        return submitOrderAndCreate(request, "image-first-frame", body);
    }

    @PostMapping("/image-first-last-frame")
    @ApiOperation("图生视频-基于首尾帧")
    public AjaxResult imageFirstLastFrame(@RequestBody PortalVideoGenRequest request) {
        if (StringUtils.isEmpty(request.getFirstUrl()) || StringUtils.isEmpty(request.getLastUrl())) {
            return AjaxResult.error("请同时上传首帧与尾帧图片");
        }
        if (StringUtils.isEmpty(request.getText())) {
            return AjaxResult.error("请输入视频描述文本");
        }
        List<ContentItem> contentList = buildTextAndFirstFrame(request.getText(), request.getFirstUrl());
        ContentItem lastFrameItem = new ContentItem();
        lastFrameItem.setType("image_url");
        lastFrameItem.setRole("last_frame");
        ImageUrl lastImageUrl = new ImageUrl();
        lastImageUrl.setUrl(request.getLastUrl());
        lastFrameItem.setImageUrl(lastImageUrl);
        contentList.add(lastFrameItem);

        ByteBodyReq body = newVideoBody(request, contentList);
        return submitOrderAndCreate(request, "image-first-last-frame", body);
    }

    @PostMapping("/image-reference")
    @ApiOperation("图生视频-基于参考图")
    public AjaxResult imageReference(@RequestBody PortalVideoGenRequest request) {
        List<ContentItem> contentList;
        if (request.getContent() != null && !request.getContent().isEmpty()) {
            contentList = new ArrayList<>(request.getContent());
            ContentItem head = contentList.get(0);
            if (head == null || !"text".equals(head.getType()) || StringUtils.isEmpty(head.getText())) {
                return AjaxResult.error("请输入视频描述文本（首条须为 type=text，可含 [图片n]/[视频n]/[音频n] 占位）");
            }
            // 保留 text + 合法 reference_*（图/音/视频）；允许“只有 text 没有参考素材”
            List<ContentItem> filtered = new ArrayList<>();
            filtered.add(head);
            for (int i = 1; i < contentList.size(); i++) {
                ContentItem it = contentList.get(i);
                if (isReferenceImageContentItem(it)
                        || isReferenceAudioContentItem(it)
                        || isReferenceVideoContentItem(it)) {
                    filtered.add(it);
                }
            }
            contentList = filtered;

            String firstRef = contentList.stream()
                    .skip(1)
                    .map(PortalVideoController::firstReferenceUrlFromItem)
                    .filter(StringUtils::isNotEmpty)
                    .findFirst()
                    .orElse(null);
            // 无参考图也允许，仅 text 提示词
            if (StringUtils.isNotEmpty(firstRef) && StringUtils.isEmpty(request.getReferenceUrl())) {
                request.setReferenceUrl(firstRef);
            }
        } else {
            if (StringUtils.isEmpty(request.getReferenceUrl())) {
                return AjaxResult.error("请上传参考图");
            }
            if (StringUtils.isEmpty(request.getText())) {
                return AjaxResult.error("请输入视频描述文本");
            }
            contentList = new ArrayList<>();
            ContentItem textItem = new ContentItem();
            textItem.setType("text");
            textItem.setText(request.getText());
            contentList.add(textItem);

            ContentItem refItem = new ContentItem();
            refItem.setType("image_url");
            refItem.setRole("reference_image");
            ImageUrl refUrl = new ImageUrl();
            refUrl.setUrl(request.getReferenceUrl());
            refItem.setImageUrl(refUrl);
            contentList.add(refItem);
        }

        ByteBodyReq body = newVideoBody(request, contentList);
        return submitOrderAndCreate(request, "image-reference", body);
    }

    private static String firstReferenceUrlFromItem(ContentItem item) {
        if (isReferenceImageContentItem(item)) {
            return item.getImageUrl().getUrl();
        }
        if (isReferenceVideoContentItem(item)) {
            return item.getVideoUrl().getUrl();
        }
        if (isReferenceAudioContentItem(item)) {
            return item.getAudioUrl().getUrl();
        }
        return null;
    }

    private static boolean isValidReferenceAssetOrHttpUrl(String raw) {
        if (StringUtils.isEmpty(raw)) {
            return false;
        }
        String url = raw.trim().toLowerCase();
        return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("asset://");
    }

    private static boolean isReferenceImageContentItem(ContentItem item) {
        if (item == null || !"image_url".equals(item.getType())) {
            return false;
        }
        if (!"reference_image".equals(item.getRole())) {
            return false;
        }
        ImageUrl iu = item.getImageUrl();
        if (iu == null || StringUtils.isEmpty(iu.getUrl())) {
            return false;
        }
        return isValidReferenceAssetOrHttpUrl(iu.getUrl());
    }

    private static boolean isReferenceVideoContentItem(ContentItem item) {
        if (item == null || !"video_url".equals(item.getType())) {
            return false;
        }
        if (!"reference_video".equals(item.getRole())) {
            return false;
        }
        ImageUrl vu = item.getVideoUrl();
        if (vu == null || StringUtils.isEmpty(vu.getUrl())) {
            return false;
        }
        return isValidReferenceAssetOrHttpUrl(vu.getUrl());
    }

    private static boolean isReferenceAudioContentItem(ContentItem item) {
        if (item == null || !"audio_url".equals(item.getType())) {
            return false;
        }
        if (!"reference_audio".equals(item.getRole())) {
            return false;
        }
        ImageUrl au = item.getAudioUrl();
        if (au == null || StringUtils.isEmpty(au.getUrl())) {
            return false;
        }
        return isValidReferenceAssetOrHttpUrl(au.getUrl());
    }

    private List<ContentItem> buildTextAndFirstFrame(String text, String firstUrl) {
        List<ContentItem> contentList = new ArrayList<>();
        ContentItem textItem = new ContentItem();
        textItem.setType("text");
        textItem.setText(text);
        contentList.add(textItem);

        ContentItem firstFrameItem = new ContentItem();
        firstFrameItem.setType("image_url");
        firstFrameItem.setRole("first_frame");
        ImageUrl firstImageUrl = new ImageUrl();
        firstImageUrl.setUrl(firstUrl);
        firstFrameItem.setImageUrl(firstImageUrl);
        contentList.add(firstFrameItem);
        return contentList;
    }

    @GetMapping("/options")
    @ApiOperation("门户视频生成可选参数（模型/比例/时长等，来自配置）")
    public AjaxResult videoParamOptions() {
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("defaults", portalVideoProperties.getDefaults());
        data.put("models", portalVideoProperties.getModels());
        data.put("ratios", portalVideoProperties.getRatios());
        data.put("durations", portalVideoProperties.getDurations());
        data.put("resolutions", portalVideoProperties.getResolutions());
        return AjaxResult.success(data);
    }

    @GetMapping("/tasks")
    @ApiOperation("查询视频生成任务列表（本用户库表分页）")
    public TableDataInfo listMyVideoTasks(AiOrder aiOrder) {
        aiOrder.setUserId(SecurityUtils.getAiUserId());
        aiOrder.setType("21");
        startPage();
        List<AiOrder> list = aiOrderService.selectAiOrderList(aiOrder);
        return getDataTable(list);
    }

    @GetMapping("/volc-tasks")
    @ApiOperation("查询视频生成任务列表（火山平台，按本用户在库中的任务 id 过滤）")
    public AjaxResult listVolcTasks(
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(defaultValue = "20") int pageSize) throws Exception {
        Long uid = SecurityUtils.getAiUserId();
        String key = apiKey();
        String raw = byteService.listVideoGenerationTasks(pageNum, pageSize, key);
        String filtered = filterVolcTasksJsonForUser(raw, uid);
        return AjaxResult.success(OM.readTree(filtered));
    }

    private String filterVolcTasksJsonForUser(String raw, Long userId) throws Exception {
        AiOrder query = new AiOrder();
        query.setUserId(userId);
        query.setType("21");
        List<AiOrder> mine = aiOrderService.selectAiOrderList(query);
        Set<String> allowed = mine.stream()
                .map(AiOrder::getResult)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toSet());

        JsonNode root = OM.readTree(raw);
        if (!(root instanceof ObjectNode)) {
            return raw;
        }
        ArrayNode arr = null;
        String arrayKey = null;
        if (root.has("items") && root.get("items").isArray()) {
            arr = (ArrayNode) root.get("items");
            arrayKey = "items";
        } else if (root.has("data") && root.get("data").isArray()) {
            arr = (ArrayNode) root.get("data");
            arrayKey = "data";
        }
        if (arr == null) {
            return raw;
        }
        ArrayNode out = OM.createArrayNode();
        for (JsonNode n : arr) {
            String tid = n.has("id") ? n.get("id").asText() : null;
            if (tid != null && allowed.contains(tid)) {
                out.add(n);
            }
        }
        ObjectNode result = ((ObjectNode) root).deepCopy();
        result.set(arrayKey, out);
        result.put("filtered_total", out.size());
        return OM.writeValueAsString(result);
    }

    @GetMapping("/tasks/{taskId}")
    @ApiOperation("查询单个视频生成任务（火山）")
    public AjaxResult getVolcTask(@PathVariable String taskId) throws Exception {
        Long uid = SecurityUtils.getAiUserId();
        AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
        if (owned == null || !uid.equals(owned.getUserId())) {
            return AjaxResult.error("无权查看该任务");
        }
        String key = apiKey();
        ByteBodyRes byteBodyRes = byteService.uploadVideo(taskId, key);
        if ("succeeded".equals(byteBodyRes.getStatus())) {
            content contentObj = byteBodyRes.getContent();
            if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
                String videoUrl = tencentCosUtil.uploadFileByUrl(contentObj.getVideo_url());
                if (videoUrl != null) {
                    contentObj.setVideo_url(videoUrl);
                    AiOrder aiOrder = new AiOrder();
                    aiOrder.setId(owned.getId());
                    aiOrder.setResult(videoUrl);
                    aiOrderService.updateAiOrder(aiOrder);
                }
            }
        }
        return AjaxResult.success(byteBodyRes);
    }

    @DeleteMapping("/tasks/{taskId}")
    @ApiOperation("删除或取消视频生成任务")
    public AjaxResult deleteOrCancelTask(@PathVariable String taskId) throws Exception {
        Long uid = SecurityUtils.getAiUserId();
        AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
        if (owned == null || !uid.equals(owned.getUserId())) {
            return AjaxResult.error("无权操作该任务");
        }
        String key = apiKey();
        AjaxResult cancelRes = byteService.cancelVideoTask(taskId, key);
        if (cancelRes.isSuccess() && owned.getStatus() != null && owned.getStatus() == 0) {
            aiOrderService.orderFailure(owned);
        }
        return cancelRes;
    }
}
