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.config.ModelCapability;
import com.yaoai.ai.providers.config.PlatformModelConfigService;
import com.yaoai.ai.providers.config.ProviderRestClientFactory;
import com.yaoai.ai.providers.config.ResolvedModelConfig;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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 API_KEY_HINT =
            "未配置视频模型 API Key，请在运营端模型配置中填写，或设置 VOLCENGINE_ARK_API_KEY";
    private static final java.util.Set<String> SUPPORTED_RATIOS = java.util.Set.of("16:9", "9:16", "1:1");

    private final ArkProperties properties;
    private final PlatformModelConfigService platformModelConfigService;
    private final ProviderRestClientFactory restClientFactory;

    public SeedanceServiceImpl(ArkProperties properties) {
        this(properties, null, new ProviderRestClientFactory());
    }

    @Autowired
    public SeedanceServiceImpl(ArkProperties properties,
                               PlatformModelConfigService platformModelConfigService,
                               ProviderRestClientFactory restClientFactory) {
        this.properties = properties;
        this.platformModelConfigService = platformModelConfigService;
        this.restClientFactory = restClientFactory;
    }

    @Override
    public String submitVideoTask(List<String> imageUrls, String prompt,
                                  int durationSeconds, boolean generateAudio, String ratio, String model) {
        ResolvedModelConfig config = resolveConfig();
        String apiKey = resolveApiKey(config);
        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 : config.modelId();
        String finalText = buildSeedancePrompt(prompt, safeRatio, safeDuration);
        Map<String, Object> body = config.isProtocol("seedance")
                ? buildSeedanceBody(imageUrls, finalText, safeModel, safeRatio, safeDuration, generateAudio)
                : buildOpenAiVideoBody(config, imageUrls, prompt, safeModel, safeRatio, safeDuration, generateAudio);

        log.info("Video submit: provider={}, protocol={}, model={}, images={}, duration={}s, audio={}, ratio={}",
                config.provider(), config.protocol(), safeModel, imageUrls.size(), safeDuration, generateAudio, safeRatio);
        try {
            TaskSubmitResponse resp = restClientFactory.create(config, Duration.ofSeconds(10), Duration.ofMinutes(2)).post()
                    .uri(config.endpoint())
                    .header("Authorization", "Bearer " + apiKey)
                    .body(body)
                    .retrieve()
                    .body(TaskSubmitResponse.class);

            String taskId = extractTaskId(resp);
            if (taskId == null || taskId.isBlank()) {
                throw new BizException(ErrorCode.INTERNAL_ERROR, "视频模型提交任务失败：返回为空");
            }
            log.info("Video task submitted: id={}", taskId);
            return taskId;
        } catch (RestClientResponseException e) {
            if (e.getStatusCode().value() == 401) {
                throw new BizException(ErrorCode.INTERNAL_ERROR, "视频模型鉴权失败，请检查 API Key 是否有效");
            }
            String responseBody = e.getResponseBodyAsString();
            log.error("Video 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,
                        "视频参考图无法被模型服务下载，请重新生成或上传公网可访问的参考图");
            }
            throw new BizException(ErrorCode.INTERNAL_ERROR,
                    "视频生成任务提交失败: HTTP " + e.getStatusCode().value());
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("Video submit failed", e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "视频生成任务提交失败: " + e.getMessage());
        }
    }

    @Override
    public VideoTaskResult getTaskStatus(String externalTaskId) {
        ResolvedModelConfig config = resolveConfig();
        String apiKey = resolveApiKey(config);
        try {
            JsonNode resp = restClientFactory.create(config, Duration.ofSeconds(10), Duration.ofMinutes(2)).get()
                    .uri(statusUri(config), externalTaskId)
                    .header("Authorization", "Bearer " + apiKey)
                    .retrieve()
                    .body(JsonNode.class);

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

            VideoTaskResult result = new VideoTaskResult();
            result.setTaskId(firstText(resp, externalTaskId, "id", "task_id"));
            result.setStatus(firstText(resp, "unknown", "status", "state"));

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

            if (isFailed(result.getStatus())) {
                result.setStatus("failed");
                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, "视频模型鉴权失败，请检查 API Key 是否有效");
            }
            log.error("Video 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("Video status check failed: taskId={}", externalTaskId, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "查询任务状态失败: " + e.getMessage());
        }
    }

    @Override
    public String getModelId() {
        return resolveConfig().modelId();
    }

    @Override
    public String getModelName() {
        return resolveConfig().modelName();
    }

    @Override
    public String getProvider() {
        return resolveConfig().provider();
    }

    private Map<String, Object> buildSeedanceBody(List<String> imageUrls, String finalText, String safeModel,
                                                  String safeRatio, int safeDuration, boolean generateAudio) {
        List<Map<String, Object>> content = new ArrayList<>();
        content.add(Map.of("type", "text", "text", finalText));
        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);
        return body;
    }

    private Map<String, Object> buildOpenAiVideoBody(ResolvedModelConfig config, List<String> imageUrls, String prompt, String safeModel,
                                                     String safeRatio, int safeDuration, boolean generateAudio) {
        Map<String, Object> body = new java.util.LinkedHashMap<>();
        body.put("model", safeModel);
        body.put("prompt", prompt == null ? "" : prompt);
        body.put("duration", safeDuration);
        body.put("aspect_ratio", safeRatio);
        body.put("image_urls", imageUrls);
        if (!config.isAgnes()) {
            body.put("generate_audio", generateAudio);
        }
        return body;
    }

    private String buildSeedancePrompt(String prompt, String safeRatio, int safeDuration) {
        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");
        return textBuilder.toString();
    }

    private ResolvedModelConfig resolveConfig() {
        if (platformModelConfigService != null) {
            return platformModelConfigService.resolve(ModelCapability.VIDEO);
        }
        return new ResolvedModelConfig(
                "volcengine",
                "Volcengine ARK",
                "seedance",
                properties.getBaseUrl(),
                properties.getApiKey(),
                properties.getVideoModel(),
                "Seedance Image-to-Video",
                "/contents/generations/tasks",
                "/contents/generations/tasks/{id}",
                null
        );
    }

    private String statusUri(ResolvedModelConfig config) {
        if (config.statusEndpoint() != null && !config.statusEndpoint().isBlank()) {
            return config.statusEndpoint();
        }
        return config.endpoint().replaceAll("/+$", "") + "/{id}";
    }

    private String resolveApiKey(ResolvedModelConfig config) {
        String apiKey = config.apiKey();
        if (apiKey == null || apiKey.isBlank()) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, API_KEY_HINT);
        }
        String normalized = apiKey.trim();
        if ("your-ark-api-key".equalsIgnoreCase(normalized)
                || "your_ark_api_key".equalsIgnoreCase(normalized)
                || PlatformModelConfigService.MASKED_API_KEY.equals(normalized)) {
            throw new BizException(ErrorCode.INTERNAL_ERROR, API_KEY_HINT);
        }
        return normalized;
    }

    private String extractTaskId(TaskSubmitResponse resp) {
        if (resp == null) return null;
        if (resp.getId() != null && !resp.getId().isBlank()) return resp.getId();
        if (resp.getTaskId() != null && !resp.getTaskId().isBlank()) return resp.getTaskId();
        return resp.getData() == null ? null : resp.getData().getId();
    }

    private boolean isSucceeded(String status) {
        return "succeeded".equalsIgnoreCase(status)
                || "success".equalsIgnoreCase(status)
                || "completed".equalsIgnoreCase(status)
                || "done".equalsIgnoreCase(status);
    }

    private boolean isFailed(String status) {
        return "failed".equalsIgnoreCase(status)
                || "fail".equalsIgnoreCase(status)
                || "error".equalsIgnoreCase(status);
    }

    private String firstText(JsonNode node, String fallback, String... fields) {
        for (String field : fields) {
            JsonNode value = node.path(field);
            if (value.isTextual() && !value.asText().isBlank()) {
                return value.asText();
            }
        }
        return fallback;
    }

    private String extractVideoUrl(JsonNode resp) {
        JsonNode content = resp.path("content");

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

        if (content.isObject()) {
            JsonNode videoUrlNode = content.path("video_url");
            if (!videoUrlNode.isMissingNode()) {
                return videoUrlNode.isObject()
                        ? videoUrlNode.path("url").asText(null)
                        : videoUrlNode.asText(null);
            }
        }

        JsonNode rootVideoUrl = resp.path("video_url");
        if (!rootVideoUrl.isMissingNode()) {
            return rootVideoUrl.isObject()
                    ? rootVideoUrl.path("url").asText(null)
                    : rootVideoUrl.asText(null);
        }

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

        JsonNode dataVideoUrl = resp.path("data").path("video_url");
        if (!dataVideoUrl.isMissingNode()) {
            return dataVideoUrl.isObject()
                    ? dataVideoUrl.path("url").asText(null)
                    : dataVideoUrl.asText(null);
        }

        String agnesVideoUrl = firstText(resp, null, "remixed_from_video_id", "url", "video");
        if (agnesVideoUrl != null && agnesVideoUrl.startsWith("http")) {
            return agnesVideoUrl;
        }

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

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

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