package kz.konturai.parser.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import kz.konturai.parser.exception.FacebookTokenExpiredException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.netty.http.client.HttpClient;

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

@Service
@Slf4j
public class FacebookPostingService {

    private static final String FACEBOOK_GRAPH_API_BASE = "https://graph.facebook.com/v19.0";

    @Value("${facebook.app.id:}")
    private String appId;

    @Value("${facebook.app.secret:}")
    private String appSecret;

    @Value("${facebook.page.token:}")
    private String pageToken;

    @Value("${facebook.page.id:918835061309575}")
    private String pageId;

    @Value("${facebook.api.timeout:30000}")
    private int timeoutMs;

    private final WebClient webClient;
    private final ObjectMapper objectMapper;

    public FacebookPostingService() {
        this.objectMapper = new ObjectMapper();

        HttpClient httpClient = HttpClient.create()
                .responseTimeout(Duration.ofMillis(60000)); // увеличен таймаут для видео

        this.webClient = WebClient.builder()
                .baseUrl(FACEBOOK_GRAPH_API_BASE)
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .codecs(configurer -> configurer.defaultCodecs()
                        .maxInMemorySize(100 * 1024 * 1024)) // 100MB для видео
                .build();
    }

    // ── Credentials ──────────────────────────────────────────────────────────

    public PageCredentials getPageCredentials() {
        return new PageCredentials(pageId, pageToken);
    }

    // ── Public posting methods ────────────────────────────────────────────────

    /**
     * Публикация текстового поста
     */
    public String postToPage(String postText, List<String> hashtags) {
        return postTextOnly(getPageCredentials(), postText, hashtags);
    }

    /**
     * Публикация поста с фото
     */
    public String postToPageWithImage(String postText, List<String> hashtags, byte[] imageData) {
        return postWithPhoto(getPageCredentials(), postText, hashtags, imageData);
    }

    /**
     * Публикация поста с видео — НОВЫЙ МЕТОД
     */
    public String postToPageWithVideo(String postText, List<String> hashtags, byte[] videoData) {
        return postWithVideo(getPageCredentials(), postText, hashtags, videoData);
    }

    // ── Core posting implementations ─────────────────────────────────────────

    /**
     * Текстовый пост через /feed
     */
    private String postTextOnly(PageCredentials creds, String postText, List<String> hashtags) {
        validateToken(creds);
        try {
            String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);

            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
            formData.add("access_token", creds.pageToken);
            formData.add("message", fullText);

            String response = webClient.post()
                    .uri(u -> u.path("/{pageId}/feed").build(creds.pageId))
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body(BodyInserters.fromMultipartData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));

            return extractPostId(response, creds.pageId);
        } catch (WebClientResponseException e) {
            handleFacebookError(e);
            return null;
        } catch (Exception e) {
            throw new RuntimeException("[Facebook] Ошибка публикации текста: " + e.getMessage(), e);
        }
    }

    /**
     * Пост с фото через /{pageId}/photos
     */
    private String postWithPhoto(PageCredentials creds, String postText, List<String> hashtags, byte[] imageData) {
        validateToken(creds);
        try {
            String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);

            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
            formData.add("access_token", creds.pageToken);
            formData.add("caption", fullText); // для фото — caption, не message
            formData.add("source", new ByteArrayResource(imageData) {
                @Override public String getFilename() { return "post.jpg"; }
            });

            String response = webClient.post()
                    .uri(u -> u.path("/{pageId}/photos").build(creds.pageId))
                    .contentType(MediaType.MULTIPART_FORM_DATA)
                    .body(BodyInserters.fromMultipartData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs * 2L));

            return extractPostId(response, creds.pageId);
        } catch (WebClientResponseException e) {
            handleFacebookError(e);
            return null;
        } catch (Exception e) {
            throw new RuntimeException("[Facebook] Ошибка публикации фото: " + e.getMessage(), e);
        }
    }

    /**
     * Пост с видео через /{pageId}/videos
     * Facebook требует отдельный endpoint для видео!
     */
    private String postWithVideo(PageCredentials creds, String postText, List<String> hashtags, byte[] videoData) {
        validateToken(creds);
        try {
            String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);

            log.info("[Facebook] Загружаю видео размером {} MB для страницы {}",
                    String.format("%.1f", videoData.length / 1024.0 / 1024.0), creds.pageId);

            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
            formData.add("access_token", creds.pageToken);
            formData.add("description", fullText); // для видео — description
            formData.add("title", extractTitle(postText)); // короткий заголовок
            formData.add("source", new ByteArrayResource(videoData) {
                @Override public String getFilename() { return "video.mp4"; }
            });

            String response = webClient.post()
                    .uri(u -> u.path("/{pageId}/videos").build(creds.pageId))
                    .contentType(MediaType.MULTIPART_FORM_DATA)
                    .body(BodyInserters.fromMultipartData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs * 6L)); // видео грузится дольше

            return extractPostId(response, creds.pageId);
        } catch (WebClientResponseException e) {
            handleFacebookError(e);
            return null;
        } catch (Exception e) {
            throw new RuntimeException("[Facebook] Ошибка публикации видео: " + e.getMessage(), e);
        }
    }

    // ── Messaging ─────────────────────────────────────────────────────────────

    public void sendPrivateMessage(String recipientId, String messageText) {
        PageCredentials creds = getPageCredentials();
        try {
            java.util.Map<String, Object> body = new java.util.HashMap<>();
            body.put("recipient", java.util.Map.of("id", recipientId));
            body.put("message", java.util.Map.of("text", messageText));
            body.put("messaging_type", "RESPONSE");

            webClient.post()
                    .uri(u -> u.path("/me/messages")
                            .queryParam("access_token", creds.pageToken).build())
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));
            log.info("[Facebook] Сообщение отправлено пользователю {}", recipientId);
        } catch (Exception e) {
            // Messaging ошибки не должны ломать основной флоу публикации
            log.warn("[Facebook] Не удалось отправить сообщение (это не критично): {}", e.getMessage());
        }
    }

    public void sendPrivateImageMessage(String recipientId, byte[] imageData) {
        PageCredentials creds = getPageCredentials();
        try {
            ByteArrayResource imageResource = new ByteArrayResource(imageData) {
                @Override public String getFilename() { return "image.jpg"; }
            };

            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
            formData.add("recipient", "{\"id\":\"" + recipientId + "\"}");
            formData.add("message", "{\"attachment\":{\"type\":\"image\", \"payload\":{\"is_reusable\":true}}}");
            formData.add("filedata", imageResource);
            formData.add("access_token", creds.pageToken);

            webClient.post()
                    .uri(u -> u.path("/me/messages").build())
                    .contentType(MediaType.MULTIPART_FORM_DATA)
                    .body(BodyInserters.fromMultipartData(formData))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs * 2));

            log.info("[Facebook] Изображение отправлено пользователю {}", recipientId);
        } catch (Exception e) {
            log.warn("[Facebook] Не удалось отправить изображение в личку (это не критично): {}", e.getMessage());
        }
    }

    // ── Token exchange ────────────────────────────────────────────────────────

    public String exchangeForLongLivedPageToken(String shortLivedUserToken, String targetPageId) {
        log.info("[Facebook] Запуск обмена токена для страницы {}", targetPageId);

        if (appId == null || appId.isBlank() || appSecret == null || appSecret.isBlank()) {
            throw new IllegalStateException("Необходимо настроить facebook.app.id и facebook.app.secret");
        }

        try {
            JsonNode userTokenNode = webClient.get()
                    .uri(u -> u.path("/oauth/access_token")
                            .queryParam("grant_type", "fb_exchange_token")
                            .queryParam("client_id", appId)
                            .queryParam("client_secret", appSecret)
                            .queryParam("fb_exchange_token", shortLivedUserToken)
                            .build())
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block(Duration.ofMillis(timeoutMs));

            String longLivedUserToken = userTokenNode.path("access_token").asText();

            JsonNode accounts = webClient.get()
                    .uri(u -> u.path("/me/accounts")
                            .queryParam("access_token", longLivedUserToken)
                            .build())
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block(Duration.ofMillis(timeoutMs));

            if (accounts != null && accounts.has("data")) {
                for (JsonNode page : accounts.get("data")) {
                    if (page.path("id").asText().equals(targetPageId)) {
                        String permToken = page.path("access_token").asText();
                        log.info("[Facebook] Получен бессрочный Page Token для {}", targetPageId);
                        return permToken;
                    }
                }
            }
            throw new RuntimeException("Страница " + targetPageId + " не найдена в аккаунте");
        } catch (Exception e) {
            log.error("[Facebook] Ошибка обмена токена: {}", e.getMessage());
            throw new RuntimeException("Ошибка Meta API: " + e.getMessage(), e);
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private void validateToken(PageCredentials creds) {
        if (creds.pageToken == null || creds.pageToken.length() < 10) {
            throw new IllegalStateException("Facebook Access Token не настроен! Проверьте application.properties");
        }
    }

    /**
     * Извлекаем короткий заголовок из текста поста (первые 100 символов до точки/переноса)
     */
    private String extractTitle(String postText) {
        if (postText == null || postText.isBlank()) return "Новый пост";
        String[] lines = postText.split("\n");
        String firstLine = lines[0].trim();
        return firstLine.length() > 100 ? firstLine.substring(0, 97) + "..." : firstLine;
    }

    private String extractPostId(String response, String pageId) throws Exception {
        JsonNode json = objectMapper.readTree(response);
        String postId = json.path("id").asText(null);
        if (postId == null || postId.isBlank()) {
            log.warn("[Facebook] Пост опубликован, но id не получен. Response: {}", response);
            return pageId + "_unknown";
        }
        log.info("[Facebook] Пост опубликован. id={}, pageId={}", postId, pageId);
        return postId;
    }

    private void handleFacebookError(WebClientResponseException e) {
        log.error("[Facebook] API Error: status={}, body={}", e.getStatusCode().value(), e.getResponseBodyAsString());
        String body = e.getResponseBodyAsString();
        if (body.contains("access token") || body.contains("expired") || body.contains("463") || body.contains("467")) {
            throw new FacebookTokenExpiredException(
                    "Токен Facebook истёк. Получите новый через утилиту обмена.",
                    "Token Expired", 190, 463, e);
        }
        throw new RuntimeException("[Facebook] API Error: " + body, e);
    }

    public static class PageCredentials {
        public final String pageId;
        public final String pageToken;
        public PageCredentials(String pageId, String pageToken) {
            this.pageId = pageId;
            this.pageToken = pageToken;
        }
    }
}
