package com.pandoli365.bibimbap.service;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.Set;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.stereotype.Component;

import com.pandoli365.bibimbap.security.SsrfSafeFetcher;
import com.pandoli365.bibimbap.security.SsrfSafeFetcher.FetchResult;

/**
 * 외부 링크의 Open Graph 메타데이터를 SSRF 방어 하에 가져와 프리뷰로 변환한다.
 */
@Component
public class OgPreviewService {

    private static final long OG_MAX_BYTES = 512L * 1024L;
    private static final Set<String> ALLOWED_CONTENT_TYPES =
            Set.of("text/html", "application/xhtml+xml");

    // DB 컬럼 길이 한계.
    private static final int TITLE_MAX = 300;
    private static final int DESCRIPTION_MAX = 600;
    private static final int IMAGE_URL_MAX = 2048;
    private static final int SITE_NAME_MAX = 200;

    private final SsrfSafeFetcher ssrfSafeFetcher;

    public OgPreviewService(SsrfSafeFetcher ssrfSafeFetcher) {
        this.ssrfSafeFetcher = ssrfSafeFetcher;
    }

    public record OgPreview(String title, String description, String imageUrl, String siteName) {
    }

    public Optional<OgPreview> fetch(String linkUrl) {
        try {
            URI uri = URI.create(linkUrl);
            Optional<FetchResult> result =
                    ssrfSafeFetcher.fetch(uri, OG_MAX_BYTES, ALLOWED_CONTENT_TYPES);
            if (result.isEmpty()) {
                return Optional.empty();
            }

            FetchResult fr = result.get();
            String html = new String(fr.body(), StandardCharsets.UTF_8);
            Document doc = Jsoup.parse(html, fr.finalUrl().toString());

            String title = cap(firstNonBlank(ogContent(doc, "og:title"), doc.title()), TITLE_MAX);
            String description = cap(ogContent(doc, "og:description"), DESCRIPTION_MAX);
            String imageUrl = cap(ogContent(doc, "og:image"), IMAGE_URL_MAX);
            String siteName = cap(ogContent(doc, "og:site_name"), SITE_NAME_MAX);

            return Optional.of(new OgPreview(title, description, imageUrl, siteName));
        } catch (Exception e) {
            // graceful: URI 파싱 실패/파싱 예외 등 전 경로 차단.
            return Optional.empty();
        }
    }

    private static String ogContent(Document doc, String property) {
        Element meta = doc.selectFirst("meta[property=" + property + "]");
        return meta != null ? meta.attr("content") : null;
    }

    private static String firstNonBlank(String a, String b) {
        if (a != null && !a.isBlank()) {
            return a;
        }
        return b;
    }

    private static String cap(String value, int max) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        if (trimmed.isEmpty()) {
            return null;
        }
        return trimmed.length() > max ? trimmed.substring(0, max) : trimmed;
    }
}
