package kz.konturai.parser.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import io.netty.resolver.DefaultAddressResolverGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.netty.http.client.HttpClient;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;

import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.time.Duration;
import java.util.*;

/**
 * Генерация видео через Google Veo 3 (Vertex AI).
 *
 * КЛЮЧЕВОЙ ФАКТ: Veo 3 требует enhancePrompt=true ОБЯЗАТЕЛЬНО.
 * Поэтому промпт пишем на английском — enhance улучшит его,
 * а не переведёт с русского криво.
 *
 * Veo 3 поддерживает звук нативно — описываем audio в промпте.
 */
@Service
public class GeminiVideoGenerationService {

    private static final Logger logger = LoggerFactory.getLogger(GeminiVideoGenerationService.class);
    private static final String CREDENTIALS_FILE_PATH = "keys/google-key.json";

    private final WebClient webClient;
    private GoogleCredentials credentials;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Value("${google.cloud.project-id}")
    private String projectId;

    @Value("${google.cloud.location:us-central1}")
    private String location;

    @Value("${google.gemini.video.model:veo-3.0-generate-001}")
    private String model;

    public GeminiVideoGenerationService() {
        HttpClient httpClient = HttpClient.create()
                .resolver(DefaultAddressResolverGroup.INSTANCE)
                .responseTimeout(Duration.ofMinutes(20));

        ExchangeStrategies strategies = ExchangeStrategies.builder()
                .codecs(configurer -> configurer.defaultCodecs()
                        .maxInMemorySize(500 * 1024 * 1024))
                .build();

        this.webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .exchangeStrategies(strategies)
                .build();
    }

    @PostConstruct
    public void initCredentials() {
        try {
            ClassPathResource resource = new ClassPathResource(CREDENTIALS_FILE_PATH);
            if (resource.exists()) {
                try (InputStream is = resource.getInputStream()) {
                    this.credentials = GoogleCredentials.fromStream(is)
                            .createScoped(Collections.singletonList(
                                    "https://www.googleapis.com/auth/cloud-platform"));
                    logger.info("VEO3: ✅ Credentials loaded. Project={}, Location={}, Model={}",
                            projectId, location, model);
                }
            } else {
                logger.error("VEO3: ❌ Credentials file '{}' NOT FOUND", CREDENTIALS_FILE_PATH);
            }
        } catch (IOException e) {
            logger.error("VEO3: ❌ Error reading credentials: {}", e.getMessage());
        }
    }

    private String getValidAccessToken() {
        if (this.credentials == null) {
            logger.error("VEO3: ❌ Credentials not initialized");
            return null;
        }
        try {
            this.credentials.refreshIfExpired();
            String token = this.credentials.getAccessToken().getTokenValue();
            if (token == null || token.isEmpty()) {
                logger.error("VEO3: ❌ Token is empty after refresh");
                return null;
            }
            return token;
        } catch (IOException e) {
            logger.error("VEO3: ❌ Token refresh failed: {}", e.getMessage());
            return null;
        }
    }

    /**
     * Генерирует видео. Принимает промпт на АНГЛИЙСКОМ языке.
     * Промпт строится в MarketingStrategyV3Service.buildVideoPromptEnglish()
     *
     * @param englishPrompt Промпт на английском языке
     * @return Байты MP4 или null при ошибке
     */
    public byte[] generateVideo(String englishPrompt) {
        if (projectId == null || projectId.trim().isEmpty()) {
            logger.error("VEO3: ❌ Project ID not configured");
            return null;
        }
        String accessToken = getValidAccessToken();
        if (accessToken == null) return null;

        try {
            return doGenerate(englishPrompt, accessToken);
        } catch (Exception e) {
            logger.error("VEO3: ❌ Unexpected error: {}", e.getMessage(), e);
            return null;
        }
    }

    private byte[] doGenerate(String prompt, String accessToken) throws Exception {

        // ═══ ШАГ 1: Запуск ═══════════════════════════════════════════════
        String generateEndpoint = String.format(
                "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predictLongRunning",
                location, projectId, location, model);

        Map<String, Object> instance = new HashMap<>();
        instance.put("prompt", prompt);

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("aspectRatio", "9:16");
        parameters.put("sampleCount", 1);
        // Veo 3 ТРЕБУЕТ enhancePrompt=true — нельзя отключить (code=3 если false)
        parameters.put("enhancePrompt", true);
        parameters.put("negativePrompt",
                "text overlay, subtitles, captions, watermark, on-screen text, " +
                        "blurry, low quality, bad anatomy, extra limbs, deformed face, " +
                        "cartoon, 3D render, CGI, artificial look, jerky motion, " +
                        "extra fingers, distorted proportions, nsfw");

        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("instances", Collections.singletonList(instance));
        requestBody.put("parameters", parameters);

        logger.info("VEO3: ▶ Starting generation | Model={}", model);
        logger.info("VEO3:   Prompt: {}",
                prompt.length() > 300 ? prompt.substring(0, 300) + "..." : prompt);

        Map<String, Object> initResponse;
        try {
            initResponse = webClient.post()
                    .uri(URI.create(generateEndpoint))
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
                    .block(Duration.ofSeconds(60));

        } catch (WebClientResponseException e) {
            logger.error("VEO3: ❌ HTTP {} on START | Body: {}",
                    e.getStatusCode().value(), e.getResponseBodyAsString());
            if (e.getStatusCode().value() == 404)
                logger.error("VEO3:   → Модель недоступна. Запросите доступ к Veo 3 в Google Cloud Console");
            if (e.getStatusCode().value() == 403)
                logger.error("VEO3:   → Нет IAM прав. Добавьте роль 'Vertex AI User' сервис-аккаунту");
            return null;
        }

        if (initResponse == null) {
            logger.error("VEO3: ❌ Null response on START");
            return null;
        }

        String operationName = (String) initResponse.get("name");
        if (operationName == null || operationName.isEmpty()) {
            logger.error("VEO3: ❌ No 'name' in response. Keys: {}", initResponse.keySet());
            return null;
        }
        logger.info("VEO3: ✅ Operation: {}", operationName);

        // ═══ ШАГ 2: Polling ═══════════════════════════════════════════════
        String fetchEndpoint = String.format(
                "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:fetchPredictOperation",
                location, projectId, location, model);

        Map<String, Object> fetchBody = new HashMap<>();
        fetchBody.put("operationName", operationName);

        int attempts = 0, maxAttempts = 120;

        while (attempts < maxAttempts) {
            Thread.sleep(15_000);
            attempts++;

            String currentToken = getValidAccessToken();
            if (currentToken == null) {
                logger.warn("VEO3: ⚠ No token on attempt {}", attempts);
                continue;
            }

            try {
                Map<String, Object> statusResponse = webClient.post()
                        .uri(URI.create(fetchEndpoint))
                        .header(HttpHeaders.AUTHORIZATION, "Bearer " + currentToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .bodyValue(fetchBody)
                        .retrieve()
                        .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
                        .block(Duration.ofSeconds(60));

                if (statusResponse == null) {
                    logger.warn("VEO3: ⚠ Null status on attempt {}", attempts);
                    continue;
                }

                if (statusResponse.containsKey("error")) {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> error = (Map<String, Object>) statusResponse.get("error");
                    logger.error("VEO3: ❌ Operation error: code={}, message={}",
                            error.get("code"), error.get("message"));
                    return null;
                }

                Object doneObj = statusResponse.get("done");
                boolean isDone = Boolean.TRUE.equals(doneObj)
                        || "true".equalsIgnoreCase(String.valueOf(doneObj));

                if (!isDone) {
                    if (attempts % 4 == 0 || attempts <= 2)
                        logger.info("VEO3: ⏳ Processing... {}/{} (~{}min)",
                                attempts, maxAttempts, attempts * 15 / 60);
                    continue;
                }

                logger.info("VEO3: ✅ DONE on attempt {} (~{}min)", attempts, attempts * 15 / 60);
                return extractVideo(statusResponse);

            } catch (WebClientResponseException e) {
                logger.error("VEO3: ❌ HTTP {} on attempt {}: {}",
                        e.getStatusCode().value(), attempts, e.getResponseBodyAsString());
                if (e.getStatusCode().is4xxClientError()) return null;
            }
        }

        logger.error("VEO3: ❌ TIMEOUT after {} min", maxAttempts * 15 / 60);
        return null;
    }

    private byte[] extractVideo(Map<String, Object> statusResponse) {
        @SuppressWarnings("unchecked")
        Map<String, Object> responseObj =
                (Map<String, Object>) statusResponse.get("response");
        if (responseObj == null) {
            logger.warn("VEO3: No 'response' field, trying root");
            responseObj = statusResponse;
        }

        String b64 = findFirstValue(responseObj, "bytesBase64Encoded");
        if (b64 != null && !b64.isEmpty()) {
            logger.info("VEO3: Found base64 ({}chars)", b64.length());
            byte[] decoded = decodeBase64(b64);
            if (decoded != null)
                logger.info("VEO3: ✅ Video decoded: {} bytes ({} MB)",
                        decoded.length, String.format("%.1f", decoded.length / 1024.0 / 1024.0));
            return decoded;
        }

        String videoUri = findFirstValue(responseObj, "videoUri");
        if (videoUri == null) videoUri = findFirstValue(responseObj, "uri");
        if (videoUri != null && !videoUri.isEmpty()) {
            logger.info("VEO3: Found URI: {}", videoUri);
            return downloadFromGcs(videoUri, getValidAccessToken());
        }

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> predictions =
                (List<Map<String, Object>>) responseObj.get("predictions");
        if (predictions != null && !predictions.isEmpty()) {
            Map<String, Object> first = predictions.get(0);
            logger.info("VEO3: predictions[0] keys: {}", first.keySet());
            b64 = (String) first.get("bytesBase64Encoded");
            if (b64 != null && !b64.isEmpty()) {
                byte[] decoded = decodeBase64(b64);
                if (decoded != null)
                    logger.info("VEO3: ✅ Video from predictions: {} bytes", decoded.length);
                return decoded;
            }
            String uri = (String) first.getOrDefault("videoUri", first.get("uri"));
            if (uri != null && !uri.isEmpty()) return downloadFromGcs(uri, getValidAccessToken());
        }

        logger.error("VEO3: ❌ Video not found. Response keys: {}", responseObj.keySet());
        return null;
    }

    private byte[] decodeBase64(String b64) {
        try {
            String clean = b64.replaceAll("\\s", "");
            if (clean.contains("base64,")) clean = clean.substring(clean.indexOf("base64,") + 7);
            return Base64.getDecoder().decode(clean);
        } catch (Exception e) {
            logger.error("VEO3: ❌ base64 decode failed: {}", e.getMessage());
            return null;
        }
    }

    private byte[] downloadFromGcs(String uri, String accessToken) {
        if (accessToken == null) return null;
        try {
            String url = uri.startsWith("gs://")
                    ? "https://storage.googleapis.com/" + uri.substring(5) : uri;
            logger.info("VEO3: Downloading from GCS: {}", url);
            byte[] bytes = webClient.get()
                    .uri(URI.create(url))
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .retrieve().bodyToMono(byte[].class).block(Duration.ofMinutes(5));
            if (bytes != null && bytes.length > 0) {
                logger.info("VEO3: ✅ GCS download: {} bytes", bytes.length);
                return bytes;
            }
            logger.error("VEO3: ❌ Empty GCS download");
            return null;
        } catch (Exception e) {
            logger.error("VEO3: ❌ GCS error: {}", e.getMessage());
            return null;
        }
    }

    private String findFirstValue(Object obj, String key) {
        if (obj instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) obj;
            if (map.containsKey(key)) {
                Object val = map.get(key);
                if (val instanceof String s && !s.isEmpty()) return s;
            }
            for (Object v : map.values()) {
                String f = findFirstValue(v, key);
                if (f != null) return f;
            }
        } else if (obj instanceof List) {
            for (Object item : (List<?>) obj) {
                String f = findFirstValue(item, key);
                if (f != null) return f;
            }
        }
        return null;
    }
}