package com.yaoai.ai.providers.service.impl;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.yaoai.ai.providers.config.ArkProperties;
import com.yaoai.ai.providers.model.VideoTaskResult;
import com.yaoai.ai.providers.service.SeedanceService;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientResponseException;

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


@Slf4j
@Service
public class SeedanceServiceImpl implements SeedanceService {

    private static final String ARK_API_KEY_HINT =
            "未配置火山方舟 API Key，请设置环境变量 VOLCENGINE_ARK_API_KEY，或在 application-local.yml 中配置 volcengine.ark.api-key";

    private final ArkProperties properties;
    private final RestClient restClient;

    public SeedanceServiceImpl(ArkProperties properties) {
        this.properties = properties;
        SimpleClientHttpRequestFactory rf = new SimpleClientHttpRequestFactory();
        rf.setConnectTimeout(Duration.ofSeconds(10));
        rf.setReadTimeout(Duration.ofMinutes(2));
        this.restClient = RestClient.builder()
                .baseUrl(properties.getBaseUrl())
                .defaultHeader("Content-Type", "application/json")
                .requestFactory(rf)
                .build();
    }

    private static final java.util.Set<String> SUPPORTED_RATIOS = java.util.Set.of("16:9", "9:16", "1:1");

    @Override
    public String submitVideoTask(List<String> imageUrls, String prompt,
                                  int durationSeconds, boolean generateAudio, String ratio, String model) {
        String apiKey = resolveApiKey();
        if (imageUrls == null || imageUrls.isEmpty()) {
            throw new BizException(ErrorCode.INVALID_PARAM, "至少需要一张参考图");
        }

        String safeRatio = (ratio != null && SUPPORTED_RATIOS.contains(ratio)) ? ratio : "16:9";
        int safeDuration = durationSeconds > 0 ? durationSeconds : 5;
        String safeModel = (model != null && !model.isBlank()) ? model : properties.getVideoModel();

        // Seedance 2.0 优先走 body 字段（generate_audio / ratio / duration / watermark），
        // 文本后缀作冗余兜底（部分老接入点仍按 --rt/--dur/--wm 解析）。
        StringBuilder textBuilder = new StringBuilder();
        if (prompt != null && !prompt.isBlank()) {
            textBuilder.append(prompt.trim());
        }
        textBuilder.append(" --rt ").append(safeRatio)
                   .append(" --dur ").append(safeDuration)
                   .append(" --wm false");
        String finalText = textBuilder.toString();

        List<Map<String, Object>> content = new ArrayList<>();
        content.add(Map.of("type", "text", "text", finalText));

        // 按顺序添加参考图 → 对应 @图1、@图2、@图3...
        for (String url : imageUrls) {
            content.add(Map.of(
                    "type", "image_url",
                    "image_url", Map.of("url", url),
                    "role", "reference_image"
            ));
        }

        Map<String, Object> body = new java.util.LinkedHashMap<>();
        body.put("model", safeModel);
        body.put("content", content);
        body.put("ratio", safeRatio);
        body.put("duration", safeDuration);
        body.put("watermark", false);
        body.put("generate_audio", generateAudio);

        log.info("Seedance submit: model={}, images={}, duration={}s, audio={}, ratio={}, finalTextSuffix='{}'",
                safeModel, imageUrls.size(), safeDuration, generateAudio, safeRatio,
                finalText.length() > 200 ? finalText.substring(finalText.length() - 200) : finalText);
        try {
            TaskSubmitResponse resp = restClient.post()
                    .uri("/contents/generations/tasks")
                    .header("Authorization", "Bearer " + apiKey)
                    .body(body)
                    .retrieve()
                    .body(TaskSubmitResponse.class);

            if (resp == null || resp.getId() == null) {
                throw new BizException(ErrorCode.INTERNAL_ERROR, "Seedance 提交任务失败：返回空");
            }
            log.info("Seedance task submitted: id={}", resp.getId());
            return resp.getId();
        } catch (RestClientResponseException e) {
            if (e.getStatusCode().value() == 401) {
                throw new BizException(ErrorCode.INTERNAL_ERROR,
                        "视频服务鉴权失败，请检查 VOLCENGINE_ARK_API_KEY 是否正确且仍有效");
            }
            String responseBody = e.getResponseBodyAsString();
            log.error("Seedance submit failed: status={}, body={}", e.getStatusCode(), responseBody, e);
            if (e.getStatusCode().value() == 400
                    && responseBody != null
                    && responseBody.contains("image_url")
                    && responseBody.contains("resource download failed")) {
                throw new BizException(ErrorCode.INTERNAL_ERROR,
                        "视频参考图无法被火山方舟下载，请重新生成角色/场景图片，确保图片已上传到 TOS 公网地址");
            }
            throw new BizException(ErrorCode.INTERNAL_ERROR,
                    "视频生成任务提交失败: HTTP " + e.getStatusCode().value());
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("Seedance submit failed", e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "视频生成任务提交失败: " + e.getMessage());
        }
    }

    @Override
    public VideoTaskResult getTaskStatus(String externalTaskId) {
        String apiKey = resolveApiKey();
        try {
            // Use JsonNode to handle flexible API response structure
            JsonNode resp = restClient.get()
                    .uri("/contents/generations/tasks/{id}", externalTaskId)
                    .header("Authorization", "Bearer " + apiKey)
                    .retrieve()
                    .body(JsonNode.class);

            if (resp == null) {
                throw new BizException(ErrorCode.INTERNAL_ERROR, "查询任务状态失败");
            }
            log.debug("Seedance task status raw response: taskId={}, body={}", externalTaskId, resp);

            VideoTaskResult result = new VideoTaskResult();
            result.setTaskId(resp.path("id").asText(externalTaskId));
            result.setStatus(resp.path("status").asText("unknown"));

            if ("succeeded".equals(result.getStatus())) {
                result.setVideoUrl(extractVideoUrl(resp));
            }

            if ("failed".equals(result.getStatus())) {
                JsonNode errorNode = resp.path("error");
                if (!errorNode.isMissingNode()) {
                    String msg = errorNode.isObject()
                            ? errorNode.path("message").asText(errorNode.toString())
                            : errorNode.asText();
                    result.setErrorMessage(msg);
                }
            }
            return result;
        } catch (RestClientResponseException e) {
            if (e.getStatusCode().value() == 401) {
                throw new BizException(ErrorCode.INTERNAL_ERROR,
                        "视频服务鉴权失败，请检查 VOLCENGINE_ARK_API_KEY 是否正确且仍有效");
            }
            log.error("Seedance status check failed: taskId={}, status={}, body={}",
                    externalTaskId, e.getStatusCode(), e.getResponseBodyAsString(), e);
            throw new BizException(ErrorCode.INTERNAL_ERROR,
                    "查询任务状态失败: HTTP " + e.getStatusCode().value());
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("Seedance status check failed: taskId={}", externalTaskId, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "查询任务状态失败: " + e.getMessage());
        }
    }

    @Override
    public String getModelId() {
        return properties.getVideoModel();
    }

    @Override
    public String getModelName() {
        return "Seedance 图生视频";
    }

    private String resolveApiKey() {
        String apiKey = properties.getApiKey();
        if (apiKey == null || apiKey.isBlank()) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, ARK_API_KEY_HINT);
        }
        String normalized = apiKey.trim();
        if ("your-ark-api-key".equalsIgnoreCase(normalized) || "your_ark_api_key".equalsIgnoreCase(normalized)) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, ARK_API_KEY_HINT);
        }
        return normalized;
    }

    // ---- helpers ----

    /**
     * Extracts video URL from ARK API task status response.
     * Handles 4 possible response structures used by Seedance 2.0:
     * 1. content[].type=="video_url" -> content[].video_url.url  (array, object)
     * 2. content[].type=="video_url" -> content[].video_url      (array, string)
     * 3. content.video_url                                        (content is object)
     * 4. video_url / output.video_url                            (root or output level)
     */
    private String extractVideoUrl(JsonNode resp) {
        JsonNode content = resp.path("content");

        // Pattern 1 & 2: content is array
        if (content.isArray()) {
            for (JsonNode item : content) {
                if ("video_url".equals(item.path("type").asText(""))) {
                    JsonNode videoUrlNode = item.path("video_url");
                    if (videoUrlNode.isObject()) {
                        return videoUrlNode.path("url").asText(null);
                    } else if (videoUrlNode.isTextual()) {
                        return videoUrlNode.asText(null);
                    }
                }
            }
        }

        // Pattern 3: content is object with video_url field
        if (content.isObject()) {
            JsonNode videoUrlNode = content.path("video_url");
            if (!videoUrlNode.isMissingNode()) {
                return videoUrlNode.isObject()
                        ? videoUrlNode.path("url").asText(null)
                        : videoUrlNode.asText(null);
            }
        }

        // Pattern 4a: root-level video_url
        JsonNode rootVideoUrl = resp.path("video_url");
        if (!rootVideoUrl.isMissingNode()) {
            return rootVideoUrl.isObject()
                    ? rootVideoUrl.path("url").asText(null)
                    : rootVideoUrl.asText(null);
        }

        // Pattern 4b: output.video_url
        JsonNode outputVideoUrl = resp.path("output").path("video_url");
        if (!outputVideoUrl.isMissingNode()) {
            return outputVideoUrl.isObject()
                    ? outputVideoUrl.path("url").asText(null)
                    : outputVideoUrl.asText(null);
        }

        log.warn("extractVideoUrl: no video URL found in response: {}", resp);
        return null;
    }

    // ---- internal response models ----

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    private static class TaskSubmitResponse {
        private String id;
        private String status;
    }

}
