package kz.konturai.parser.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import kz.konturai.parser.exception.LinkedInTokenExpiredException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
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
public class LinkedInPostingService {

    private static final Logger logger = LoggerFactory.getLogger(LinkedInPostingService.class);
    private static final String LINKEDIN_API_BASE = "https://api.linkedin.com/v2";

    private final WebClient webClient;
    private final ObjectMapper objectMapper;

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

    public LinkedInPostingService() {
        HttpClient httpClient = HttpClient.create()
                .responseTimeout(Duration.ofMillis(30000));

        this.webClient = WebClient.builder()
                .baseUrl(LINKEDIN_API_BASE)
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        this.objectMapper = new ObjectMapper();
    }

    /**
     * Публикует пост в LinkedIn через REST API v2
     * 
     * @param accessToken Access Token пользователя
     * @param postText    Текст поста
     * @param hashtags    Список хештегов
     * @return ID опубликованного поста
     * @throws LinkedInTokenExpiredException если токен истек
     * @throws RuntimeException              если публикация не удалась
     */
    public String postToLinkedIn(String accessToken, String postText, List<String> hashtags) {
        try {
            // Формируем полный текст поста с контактами и хештегами
            String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags);

            // Получаем URN пользователя
            String personUrn = getPersonUrn(accessToken);

            // Публикуем пост
            String postId = publishPost(accessToken, personUrn, fullPostText, null);

            logger.info("Successfully posted to LinkedIn. Post ID: {}", postId);
            return postId;

        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseLinkedInError(e);
            }
            logger.error("LinkedIn API error: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to post to LinkedIn: " + e.getMessage(), e);
        } catch (LinkedInTokenExpiredException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Unexpected error posting to LinkedIn", e);
            throw new RuntimeException("Failed to post to LinkedIn", e);
        }
    }

    /**
     * Публикует пост в LinkedIn с изображением через REST API v2
     * 
     * @param accessToken      Access Token пользователя
     * @param postText         Текст поста
     * @param hashtags         Список хештегов
     * @param imageData        Данные изображения
     * @param imageContentType MIME тип изображения (например, "image/png")
     * @return ID опубликованного поста
     * @throws LinkedInTokenExpiredException если токен истек
     * @throws RuntimeException              если публикация не удалась
     */
    public String postToLinkedInWithImage(String accessToken, String postText, List<String> hashtags,
            byte[] imageData, String imageContentType) {
        try {
            // Формируем полный текст поста с контактами и хештегами
            String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags);

            // Получаем URN пользователя
            String personUrn = getPersonUrn(accessToken);

            // Загружаем изображение и получаем его URN
            String imageUrn = uploadImage(accessToken, imageData, imageContentType);

            // Публикуем пост с изображением
            String postId = publishPost(accessToken, personUrn, fullPostText, imageUrn);

            logger.info("Successfully posted to LinkedIn with image. Post ID: {}", postId);
            return postId;

        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseLinkedInError(e);
            }
            logger.error("LinkedIn API error: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to post to LinkedIn: " + e.getMessage(), e);
        } catch (LinkedInTokenExpiredException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Unexpected error posting to LinkedIn", e);
            throw new RuntimeException("Failed to post to LinkedIn", e);
        }
    }

    /**
     * Получает URN пользователя через /me endpoint
     */
    private String getPersonUrn(String accessToken) {
        try {
            String response = webClient.get()
                    .uri("/me")
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));

            JsonNode jsonNode = objectMapper.readTree(response);
            String id = jsonNode.get("id").asText();
            
            // Формируем URN в формате urn:li:person:{id}
            return "urn:li:person:" + id;
        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseLinkedInError(e);
            }
            logger.error("Failed to get LinkedIn person URN: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to get LinkedIn person URN: " + e.getMessage(), e);
        } catch (JsonProcessingException e) {
            logger.error("Failed to parse LinkedIn person URN response", e);
            throw new RuntimeException("Failed to parse LinkedIn person URN response", e);
        } catch (LinkedInTokenExpiredException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Failed to get LinkedIn person URN", e);
            throw new RuntimeException("Failed to get LinkedIn person URN", e);
        }
    }

    /**
     * Загружает изображение в LinkedIn и возвращает его URN
     */
    private String uploadImage(String accessToken, byte[] imageData, String imageContentType) {
        try {
            // Шаг 1: Инициализируем загрузку изображения
            ObjectNode initializeRequest = objectMapper.createObjectNode();
            ObjectNode initializeUploadRequest = objectMapper.createObjectNode();
            initializeUploadRequest.put("owner", getPersonUrn(accessToken));
            initializeRequest.set("initializeUploadRequest", initializeUploadRequest);

            String initializeResponse = webClient.post()
                    .uri("/images?action=initializeUpload")
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(initializeRequest.toString()))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));

            JsonNode initializeJson = objectMapper.readTree(initializeResponse);
            JsonNode value = initializeJson.get("value");
            String uploadUrl = value.get("uploadUrl").asText();
            String imageUrn = value.get("image").asText();

            // Шаг 2: Загружаем изображение по полученному URL
            uploadImageToUrl(uploadUrl, imageData, imageContentType);

            return imageUrn;
        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseLinkedInError(e);
            }
            logger.error("LinkedIn API error uploading image: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to upload image to LinkedIn: " + e.getMessage(), e);
        } catch (JsonProcessingException e) {
            logger.error("Failed to parse LinkedIn image upload response", e);
            throw new RuntimeException("Failed to parse LinkedIn image upload response", e);
        } catch (Exception e) {
            logger.error("Failed to upload image to LinkedIn", e);
            throw new RuntimeException("Failed to upload image to LinkedIn", e);
        }
    }

    /**
     * Загружает изображение по указанному URL
     */
    private void uploadImageToUrl(String uploadUrl, byte[] imageData, String imageContentType) {
        try {
            WebClient uploadClient = WebClient.builder()
                    .build();

            uploadClient.put()
                    .uri(uploadUrl)
                    .contentType(MediaType.parseMediaType(imageContentType))
                    .body(BodyInserters.fromValue(imageData))
                    .retrieve()
                    .bodyToMono(Void.class)
                    .block(Duration.ofMillis(timeoutMs * 2));

            logger.debug("Image uploaded successfully to LinkedIn");
        } catch (Exception e) {
            logger.error("Failed to upload image to LinkedIn upload URL", e);
            throw new RuntimeException("Failed to upload image to LinkedIn upload URL", e);
        }
    }

    /**
     * Публикует пост в LinkedIn через /ugcPosts endpoint
     */
    private String publishPost(String accessToken, String personUrn, String message, String imageUrn)
            throws JsonProcessingException {
        try {
            // Создаем JSON структуру для UGC Post
            ObjectNode requestBody = objectMapper.createObjectNode();
            
            // Author
            requestBody.put("author", personUrn);
            
            // Lifecycle state
            requestBody.put("lifecycleState", "PUBLISHED");
            
            // Specific content
            ObjectNode specificContent = objectMapper.createObjectNode();
            ObjectNode shareContent = objectMapper.createObjectNode();
            
            // Share commentary (текст поста)
            ObjectNode shareCommentary = objectMapper.createObjectNode();
            shareCommentary.put("text", message);
            shareContent.set("shareCommentary", shareCommentary);
            
            // Share media category
            if (imageUrn != null && !imageUrn.isEmpty()) {
                shareContent.put("shareMediaCategory", "IMAGE");
                
                // Media
                com.fasterxml.jackson.databind.node.ArrayNode mediaArray = objectMapper.createArrayNode();
                ObjectNode mediaItem = objectMapper.createObjectNode();
                mediaItem.put("status", "READY");
                mediaItem.put("media", imageUrn);
                mediaArray.add(mediaItem);
                
                ObjectNode media = objectMapper.createObjectNode();
                media.set("media", mediaArray);
                shareContent.set("media", media);
            } else {
                shareContent.put("shareMediaCategory", "NONE");
            }
            
            specificContent.set("com.linkedin.ugc.ShareContent", shareContent);
            requestBody.set("specificContent", specificContent);
            
            // Visibility
            ObjectNode visibility = objectMapper.createObjectNode();
            visibility.put("com.linkedin.ugc.MemberNetworkVisibility", "PUBLIC");
            requestBody.set("visibility", visibility);

            String response = webClient.post()
                    .uri("/ugcPosts")
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(requestBody.toString()))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block(Duration.ofMillis(timeoutMs));

            JsonNode jsonNode = objectMapper.readTree(response);
            String postId = jsonNode.get("id").asText();
            
            return postId;
        } catch (WebClientResponseException e) {
            if (isTokenExpiredError(e)) {
                throw parseLinkedInError(e);
            }
            logger.error("LinkedIn API error: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
            throw new RuntimeException("Failed to publish post: " + e.getMessage(), e);
        }
    }

    /**
     * Проверяет, является ли ошибка истечением токена
     * LinkedIn возвращает HTTP 401 (Unauthorized) для истекших токенов
     */
    private boolean isTokenExpiredError(WebClientResponseException e) {
        return e.getStatusCode().value() == 401;
    }

    /**
     * Парсит ошибку LinkedIn API и создает исключение для истекшего токена
     */
    private LinkedInTokenExpiredException parseLinkedInError(WebClientResponseException e) {
        try {
            String responseBody = e.getResponseBodyAsString();
            String errorMessage = "Access token expired or invalid";
            
            if (responseBody != null && !responseBody.isEmpty()) {
                try {
                    JsonNode errorNode = objectMapper.readTree(responseBody);
                    if (errorNode.has("message")) {
                        errorMessage = errorNode.get("message").asText();
                    } else if (errorNode.has("error")) {
                        JsonNode error = errorNode.get("error");
                        if (error.has("message")) {
                            errorMessage = error.get("message").asText();
                        }
                    }
                } catch (Exception ex) {
                    logger.warn("Failed to parse LinkedIn error response", ex);
                }
            }

            String userMessage = "LinkedIn access token has expired. Please update your credentials. " +
                    "Error: " + errorMessage;

            return new LinkedInTokenExpiredException(userMessage, errorMessage, e.getStatusCode().value(), e);
        } catch (Exception ex) {
            logger.warn("Failed to parse LinkedIn error details", ex);
            return new LinkedInTokenExpiredException(
                    "LinkedIn access token has expired. Please update your credentials.",
                    "Access token expired",
                    401,
                    e);
        }
    }
}
