package com.storycove.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.storycove.dto.HtmlSanitizationConfigDto;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

@Service
public class HtmlSanitizationService {
    
    private static final Logger logger = LoggerFactory.getLogger(HtmlSanitizationService.class);
    private final ObjectMapper objectMapper;
    private Safelist allowlist;
    private HtmlSanitizationConfigDto config;
    
    public HtmlSanitizationService(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    
    @PostConstruct
    public void initializeSanitization() {
        loadConfiguration();
        createSafelist();
    }
    
    private void loadConfiguration() {
        try {
            ClassPathResource resource = new ClassPathResource("html-sanitization-config.json");
            try (InputStream inputStream = resource.getInputStream()) {
                this.config = objectMapper.readValue(inputStream, HtmlSanitizationConfigDto.class);
                logger.info("Loaded HTML sanitization configuration with {} allowed tags", 
                           config.getAllowedTags().size());
            }
        } catch (IOException e) {
            logger.error("Failed to load HTML sanitization configuration, using fallback", e);
            createFallbackConfiguration();
        }
    }
    
    private void createFallbackConfiguration() {
        // Fallback configuration if JSON loading fails
        this.config = new HtmlSanitizationConfigDto();
        this.config.setAllowedTags(List.of(
            "p", "br", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6",
            "b", "strong", "i", "em", "u", "s", "strike", "del", "ins",
            "sup", "sub", "small", "big", "mark", "pre", "code",
            "ul", "ol", "li", "dl", "dt", "dd", "a", "img",
            "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption",
            "blockquote", "cite", "q", "hr"
        ));
        this.config.setAllowedCssProperties(List.of(
            "color", "background-color", "font-size", "font-weight", 
            "font-style", "text-align", "text-decoration", "margin", "padding"
        ));
    }
    
    private void createSafelist() {
        this.allowlist = Safelist.relaxed();

        // Add allowed tags
        if (config.getAllowedTags() != null) {
            config.getAllowedTags().forEach(allowlist::addTags);
        }

        // Add allowed attributes
        if (config.getAllowedAttributes() != null) {
            for (Map.Entry<String, List<String>> entry : config.getAllowedAttributes().entrySet()) {
                String tag = entry.getKey();
                List<String> attributes = entry.getValue();
                if (attributes != null) {
                    allowlist.addAttributes(tag, attributes.toArray(new String[0]));
                }
            }
        }

        // Special handling for img tags - allow all src attributes and validate later
        allowlist.removeProtocols("img", "src", "http", "https");
        // This is the key: preserve relative URLs by not restricting them
        allowlist.preserveRelativeLinks(true);

        // Configure allowed protocols for other attributes
        if (config.getAllowedProtocols() != null) {
            for (Map.Entry<String, Map<String, List<String>>> tagEntry : config.getAllowedProtocols().entrySet()) {
                String tag = tagEntry.getKey();
                Map<String, List<String>> attributeProtocols = tagEntry.getValue();

                if (attributeProtocols != null) {
                    for (Map.Entry<String, List<String>> attrEntry : attributeProtocols.entrySet()) {
                        String attribute = attrEntry.getKey();
                        List<String> protocols = attrEntry.getValue();

                        if (protocols != null && !("img".equals(tag) && "src".equals(attribute))) {
                            // Skip img src since we handled it above
                            allowlist.addProtocols(tag, attribute, protocols.toArray(new String[0]));
                        }
                    }
                }
            }
        }

        logger.info("Configured Jsoup Safelist with preserveRelativeLinks=true for local image URLs");
        
        // Remove specific attributes if needed (deprecated in favor of protocol control)
        if (config.getRemovedAttributes() != null) {
            for (Map.Entry<String, List<String>> entry : config.getRemovedAttributes().entrySet()) {
                String tag = entry.getKey();
                List<String> attributes = entry.getValue();
                if (attributes != null) {
                    attributes.forEach(attr -> allowlist.removeAttributes(tag, attr));
                }
            }
        }
        
        // Add allowed CSS properties for style attributes
        if (config.getAllowedCssProperties() != null) {
            config.getAllowedCssProperties().forEach(property -> 
                allowlist.addProtocols("style", property));
        }
        
        logger.info("Created Jsoup Safelist with configuration");
    }
    
    /**
     * Get the current sanitization configuration for sharing with frontend
     */
    public HtmlSanitizationConfigDto getConfiguration() {
        return config;
    }
    
    /**
     * Preprocess HTML to extract images from figure tags before sanitization
     */
    private String preprocessFigureTags(String html) {
        if (html == null || html.trim().isEmpty()) {
            return html;
        }

        try {
            org.jsoup.nodes.Document doc = Jsoup.parse(html);
            org.jsoup.select.Elements figures = doc.select("figure");

            for (org.jsoup.nodes.Element figure : figures) {
                // Find img tags within the figure
                org.jsoup.select.Elements images = figure.select("img");

                if (!images.isEmpty()) {
                    // Extract the first image and replace the figure with it
                    org.jsoup.nodes.Element img = images.first();

                    // Check if there's a figcaption to preserve as alt text
                    org.jsoup.select.Elements figcaptions = figure.select("figcaption");
                    if (!figcaptions.isEmpty() && !img.hasAttr("alt")) {
                        String captionText = figcaptions.first().text();
                        if (captionText != null && !captionText.trim().isEmpty()) {
                            img.attr("alt", captionText);
                        }
                    }

                    // Replace the figure element with just the img
                    figure.replaceWith(img.clone());
                    logger.debug("Extracted image from figure tag: {}", img.attr("src"));
                } else {
                    // No images in figure, remove it entirely
                    figure.remove();
                    logger.debug("Removed figure tag without images");
                }
            }

            return doc.body().html();
        } catch (Exception e) {
            logger.warn("Failed to preprocess figure tags, returning original HTML: {}", e.getMessage());
            return html;
        }
    }

    public String sanitize(String html) {
        if (html == null || html.trim().isEmpty()) {
            return "";
        }

        logger.debug("Sanitizing HTML content (length: {} characters)", html.length());

        // Preprocess to extract images from figure tags
        String preprocessed = preprocessFigureTags(html);

        String saniztedHtml = Jsoup.clean(preprocessed, allowlist.preserveRelativeLinks(true));
        logger.debug("Sanitization complete (output length: {} characters)", saniztedHtml.length());
        return saniztedHtml;
    }
    
    public String extractPlainText(String html) {
        if (html == null || html.trim().isEmpty()) {
            return "";
        }
        
        return Jsoup.parse(html).text();
    }
    
    public boolean isClean(String html) {
        if (html == null || html.trim().isEmpty()) {
            return true;
        }
        
        String cleaned = sanitize(html);
        return html.equals(cleaned);
    }
    
    public String sanitizeAndExtractText(String html) {
        String sanitized = sanitize(html);
        return extractPlainText(sanitized);
    }
}