package com.ruoyi.api;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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());
    }

    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());
        }
    }

    private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) {
        AiOrder aiOrder = aiOrderService.getAiOrder(
                StringUtils.isNotEmpty(req.getFunctionType()) ? req.getFunctionType() : "21");
        if (aiOrder == null) {
            return AjaxResult.error(-1, "You have a low balance, please recharge");
        }
        try {
            aiOrder.setText(req.getText());
            aiOrder.setMode(mode);
            applyOrderImages(aiOrder, req);
            if (req.getDuration() != null) {
                aiOrder.setDuration(req.getDuration());
            } else if (byteBodyReq.getDuration() != null) {
                aiOrder.setDuration(byteBodyReq.getDuration());
            }
            if (StringUtils.isNotEmpty(byteBodyReq.getResolution())) {
                aiOrder.setResolution(byteBodyReq.getResolution());
            }
            if (StringUtils.isNotEmpty(byteBodyReq.getRatio())) {
                aiOrder.setRatio(byteBodyReq.getRatio());
            }
            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");
            }
            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) {
        if (StringUtils.isEmpty(request.getReferenceUrl())) {
            return AjaxResult.error("请上传参考图");
        }
        if (StringUtils.isEmpty(request.getText())) {
            return AjaxResult.error("请输入视频描述文本");
        }
        List<ContentItem> 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 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.getAiOrderByResult(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.getAiOrderByResult(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;
    }
}
