package kz.konturai.parser.config;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.lang.NonNull;

import java.util.*;

@Configuration
public class MongoConfig {

    private static final Logger logger = LoggerFactory.getLogger(MongoConfig.class);
    private static final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule());

    @Bean
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<>();
        converters.add(new StringToMapConverter());
        converters.add(new DocumentToMapConverter());
        converters.add(new MapToStringConverter());

        // ДОБАВИЛИ НАШ НОВЫЙ КОНВЕРТЕР СЮДА
        converters.add(new StringToDoubleMongoConverter());

        return new MongoCustomConversions(converters);
    }

    /**
     * Конвертер для чтения: String -> Double
     * Спасает от NumberFormatException, если в базе старый текст (например, "Средний")
     */
    @ReadingConverter
    public static class StringToDoubleMongoConverter implements Converter<String, Double> {
        @Override
        public Double convert(@NonNull String source) {
            try {
                return Double.parseDouble(source.trim());
            } catch (NumberFormatException e) {
                logger.warn("StringToDoubleMongoConverter: Не удалось преобразовать '{}' в Double. Возвращаем 0.0", source);
                return 0.0;
            }
        }
    }

    /**
     * Конвертер для чтения: String -> Map<String, Object>
     * Обрабатывает случай, когда target_audience хранится как строка в MongoDB
     */
    @ReadingConverter
    public static class StringToMapConverter implements Converter<String, Map<String, Object>> {
        @Override
        public Map<String, Object> convert(String source) {
            long startTime = System.currentTimeMillis();
            int sourceLength = source != null ? source.length() : 0;
            logger.info("StringToMapConverter: Starting conversion. Source length: {} chars", sourceLength);

            if (source == null || source.trim().isEmpty()) {
                logger.debug("StringToMapConverter: Source is null or empty, returning empty Map");
                return new HashMap<>();
            }

            try {
                if (source.trim().startsWith("{") || source.trim().startsWith("[")) {
                    logger.debug("StringToMapConverter: Parsing JSON string (starts with {} or [)",
                            source.trim().charAt(0));
                    long parseStartTime = System.currentTimeMillis();

                    Map<String, Object> result = objectMapper.readValue(source,
                            new TypeReference<Map<String, Object>>() {
                            });

                    long parseDuration = System.currentTimeMillis() - parseStartTime;
                    int resultSize = result != null ? result.size() : 0;

                    logger.info("StringToMapConverter: Successfully parsed JSON string to Map. " +
                                    "Source length: {} chars, Result size: {} keys, Parse duration: {} ms",
                            sourceLength, resultSize, parseDuration);

                    if (parseDuration > 1000) {
                        logger.warn("StringToMapConverter: Parse took {} ms, which is longer than expected",
                                parseDuration);
                    }

                    if (sourceLength > 100000) {
                        logger.warn(
                                "StringToMapConverter: Parsed very large JSON ({} chars). This may cause performance issues.",
                                sourceLength);
                    }

                    long totalDuration = System.currentTimeMillis() - startTime;
                    logger.debug("StringToMapConverter: Conversion completed. Total duration: {} ms", totalDuration);

                    return result;
                }
                logger.warn("StringToMapConverter: target_audience is a plain string, not JSON: {}", source);
                return new HashMap<>();
            } catch (Exception e) {
                logger.error("StringToMapConverter: Error converting string to Map. Source length: {} chars",
                        sourceLength, e);
                logger.error("StringToMapConverter: Stack trace: ", e);
                return new HashMap<>();
            }
        }
    }

    /**
     * Конвертер для чтения: Document -> Map<String, Object>
     * Обрабатывает случай, когда MongoDB возвращает Document вместо Map
     */
    @ReadingConverter
    public static class DocumentToMapConverter implements Converter<Document, Map<String, Object>> {
        private static final int MAX_RECURSION_DEPTH = 50;

        @Override
        public Map<String, Object> convert(Document source) {
            return convert(source, 0);
        }

        private Map<String, Object> convert(Document source, int depth) {
            logger.debug("DocumentToMapConverter: Converting Document to Map (depth: {})", depth);

            if (source == null) {
                logger.debug("DocumentToMapConverter: Source is null, returning empty Map");
                return new HashMap<>();
            }

            if (depth > MAX_RECURSION_DEPTH) {
                logger.error(
                        "DocumentToMapConverter: Maximum recursion depth ({}) exceeded. Possible circular reference!",
                        MAX_RECURSION_DEPTH);
                return new HashMap<>();
            }

            long startTime = System.currentTimeMillis();
            int keyCount = source.keySet().size();
            logger.debug("DocumentToMapConverter: Processing Document with {} keys at depth {}", keyCount, depth);

            Map<String, Object> result = new HashMap<>();
            int nestedCount = 0;

            for (String key : source.keySet()) {
                Object value = source.get(key);
                if (value instanceof Document) {
                    nestedCount++;
                    logger.debug("DocumentToMapConverter: Found nested Document for key: {} at depth {}", key, depth);
                    result.put(key, convert((Document) value, depth + 1));
                } else if (value instanceof org.bson.types.BSONTimestamp ||
                        value instanceof org.bson.types.ObjectId ||
                        value instanceof java.util.Date) {
                    result.put(key, value.toString());
                } else {
                    result.put(key, value);
                }
            }

            long duration = System.currentTimeMillis() - startTime;
            logger.info("DocumentToMapConverter: Successfully converted Document to Map. " +
                            "Keys: {}, Nested documents: {}, Depth: {}, Duration: {} ms",
                    result.size(), nestedCount, depth, duration);

            if (duration > 1000) {
                logger.warn("DocumentToMapConverter: Conversion took {} ms at depth {}, which is longer than expected",
                        duration, depth);
            }

            if (keyCount > 100) {
                logger.warn("DocumentToMapConverter: Converted Document with {} keys, which is unusually large",
                        keyCount);
            }

            return result;
        }
    }

    /**
     * Конвертер для записи: Map<String, Object> -> String
     * Используется при сохранении reportData в MongoDB
     */
    @WritingConverter
    public static class MapToStringConverter implements Converter<Map<String, Object>, String> {
        @Override
        public String convert(Map<String, Object> source) {
            logger.info("MapToStringConverter: Starting conversion of Map to String");
            if (source == null || source.isEmpty()) {
                logger.debug("MapToStringConverter: Source is null or empty, returning null");
                return null;
            }

            logger.debug("MapToStringConverter: Source Map size: {} keys. Keys: {}",
                    source.size(), source.keySet());

            try {
                logger.debug("MapToStringConverter: Starting JSON serialization...");
                long startTime = System.currentTimeMillis();
                String result = objectMapper.writeValueAsString(source);
                long duration = System.currentTimeMillis() - startTime;
                logger.info(
                        "MapToStringConverter: Successfully converted Map to String. Result length: {} chars, Duration: {} ms",
                        result != null ? result.length() : 0, duration);

                if (duration > 5000) {
                    logger.warn("MapToStringConverter: Conversion took {} ms, which is longer than expected", duration);
                }

                return result;
            } catch (Exception e) {
                logger.error("MapToStringConverter: Error converting Map to String. Map size: {}, Keys: {}",
                        source.size(), source.keySet(), e);
                logger.error("MapToStringConverter: Stack trace: ", e);
                return null;
            }
        }
    }
}