package at.procon.dip.ingestion.util;

import at.procon.dip.domain.document.DocumentFamily;
import at.procon.dip.domain.document.DocumentType;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.springframework.util.StringUtils;

/**
 * Shared utility methods for Phase 4 generic ingestion.
 */
public final class DocumentImportSupport {

    private static final Set<String> TEXT_EXTENSIONS = Set.of(
            "txt", "log", "csv", "tsv", "json", "yaml", "yml", "xml", "xsd", "xslt", "html", "htm", "md", "markdown"
    );

    private static final Map<String, String> KNOWN_MIME_BY_EXTENSION = Map.ofEntries(
            Map.entry("pdf", "application/pdf"),
            Map.entry("html", "text/html"),
            Map.entry("htm", "text/html"),
            Map.entry("md", "text/markdown"),
            Map.entry("markdown", "text/markdown"),
            Map.entry("xml", "application/xml"),
            Map.entry("xsd", "application/xml"),
            Map.entry("xslt", "application/xml"),
            Map.entry("txt", "text/plain"),
            Map.entry("log", "text/plain"),
            Map.entry("csv", "text/csv"),
            Map.entry("tsv", "text/tab-separated-values"),
            Map.entry("json", "application/json"),
            Map.entry("yaml", "application/yaml"),
            Map.entry("yml", "application/yaml"),
            Map.entry("eml", "message/rfc822"),
            Map.entry("zip", "application/zip"),
            Map.entry("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
            Map.entry("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
            Map.entry("xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"),
            Map.entry("xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"),
            Map.entry("xls", "application/vnd.ms-excel"),
            Map.entry("ods", "application/vnd.oasis.opendocument.spreadsheet"),
            Map.entry("doc", "application/msword"),
            Map.entry("ppt", "application/vnd.ms-powerpoint"),
            Map.entry("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
            Map.entry("odt", "application/vnd.oasis.opendocument.text"),
            Map.entry("odp", "application/vnd.oasis.opendocument.presentation")
    );

    private DocumentImportSupport() {
    }

    public static String extensionOf(String fileName) {
        if (!StringUtils.hasText(fileName) || !fileName.contains(".")) {
            return "";
        }
        return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
    }

    public static boolean isLikelyTextMime(String mediaType) {
        if (!StringUtils.hasText(mediaType)) {
            return false;
        }
        String normalized = normalizeMediaType(mediaType);
        return normalized.startsWith("text/")
                || normalized.contains("application/json")
                || normalized.contains("application/xml")
                || normalized.contains("javascript")
                || normalized.equals("application/xhtml+xml");
    }

    public static boolean isLikelyTextExtension(String extension) {
        if (!StringUtils.hasText(extension)) {
            return false;
        }
        String normalized = extension.toLowerCase(Locale.ROOT);
        return normalized.equals("txt")
                || normalized.equals("xml")
                || normalized.equals("html")
                || normalized.equals("htm")
                || normalized.equals("md")
                || normalized.equals("markdown")
                || normalized.equals("json")
                || normalized.equals("yaml")
                || normalized.equals("yml")
                || normalized.equals("csv")
                || normalized.equals("log")
                || normalized.equals("eml");
    }

    public static String normalizeMediaType(String mediaType) {
        if (!StringUtils.hasText(mediaType)) {
            return null;
        }
        int idx = mediaType.indexOf(';');
        String result = idx >= 0 ? mediaType.substring(0, idx) : mediaType;
        return result.trim().toLowerCase(Locale.ROOT);
    }

    public static String inferKnownMimeType(String fileName, String mediaType) {
        String normalized = normalizeMediaType(mediaType);
        String extension = extensionOf(fileName);
        String inferred = KNOWN_MIME_BY_EXTENSION.get(extension);
        if (!StringUtils.hasText(normalized)) {
            return inferred;
        }
        if ("application/octet-stream".equals(normalized)
                || "binary/octet-stream".equals(normalized)
                || "application/x-download".equals(normalized)) {
            return inferred != null ? inferred : normalized;
        }
        return normalized;
    }

    public static boolean isSpreadsheetMime(String mediaType) {
        String normalized = normalizeMediaType(mediaType);
        if (!StringUtils.hasText(normalized)) {
            return false;
        }
        return normalized.equals("application/vnd.ms-excel")
                || normalized.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                || normalized.equals("application/vnd.ms-excel.sheet.macroenabled.12")
                || normalized.equals("application/vnd.ms-excel.sheet.binary.macroenabled.12")
                || normalized.equals("application/vnd.oasis.opendocument.spreadsheet")
                || normalized.equals("text/csv")
                || normalized.equals("text/tab-separated-values")
                || normalized.equals("application/csv")
                || normalized.equals("application/x-csv");
    }

    public static boolean isSpreadsheetExtension(String extension) {
        if (!StringUtils.hasText(extension)) {
            return false;
        }
        String normalized = extension.toLowerCase(Locale.ROOT);
        return normalized.equals("xls")
                || normalized.equals("xlsx")
                || normalized.equals("xlsm")
                || normalized.equals("xlsb")
                || normalized.equals("ods")
                || normalized.equals("csv")
                || normalized.equals("tsv");
    }

    public static String inferKnownMimeType(String extension) {
        if (!StringUtils.hasText(extension)) {
            return null;
        }
        return switch (extension.toLowerCase(Locale.ROOT)) {
            case "pdf" -> "application/pdf";
            case "html", "htm" -> "text/html";
            case "md", "markdown" -> "text/markdown";
            case "xml", "xsd", "xslt" -> "application/xml";
            case "txt", "log" -> "text/plain";
            case "csv" -> "text/csv";
            case "tsv" -> "text/tab-separated-values";
            case "json" -> "application/json";
            case "yaml", "yml" -> "application/yaml";
            case "eml" -> "message/rfc822";
            case "zip" -> "application/zip";
            case "xls" -> "application/vnd.ms-excel";
            case "xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            case "xlsm" -> "application/vnd.ms-excel.sheet.macroenabled.12";
            case "xlsb" -> "application/vnd.ms-excel.sheet.binary.macroenabled.12";
            case "ods" -> "application/vnd.oasis.opendocument.spreadsheet";
            default -> null;
        };
    }

    public static boolean isClearlyTextExtension(String extension) {
        return TEXT_EXTENSIONS.contains(extensionOf(extension));
    }

    public static DocumentFamily familyFor(DocumentType documentType) {
        return switch (documentType) {
            case TED_PACKAGE, TED_NOTICE, TED_NOTICE_LOT -> DocumentFamily.PROCUREMENT;
            case TIME_ENTRY -> DocumentFamily.TIME;
            case EMAIL, MIME_MESSAGE -> DocumentFamily.MAIL;
            case PDF, DOCX, HTML, XML_GENERIC, TEXT, MARKDOWN, ZIP_ARCHIVE, GENERIC_BINARY, UNKNOWN ->
                    DocumentFamily.GENERIC;
        };
    }

    public static String safeUtf8(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }

    public static String firstNonBlank(Map<String, String> values, String... keys) {
        for (String key : keys) {
            if (values == null) {
                continue;
            }
            String value = values.get(key);
            if (StringUtils.hasText(value)) {
                return value;
            }
        }
        return null;
    }

    public static String ellipsize(String text, int maxLength) {
        if (text == null || text.length() <= maxLength) {
            return text;
        }
        if (maxLength <= 3) {
            return text.substring(0, maxLength);
        }
        return text.substring(0, maxLength - 3) + "...";
    }

    public static boolean containsZeroByte(byte[] bytes) {
        if (bytes == null) {
            return false;
        }
        for (byte b : bytes) {
            if (b == 0) {
                return true;
            }
        }
        return false;
    }

    public static boolean looksBinary(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return false;
        }
        if (containsZeroByte(bytes)) {
            return true;
        }
        int sample = Math.min(bytes.length, 512);
        int suspicious = 0;
        for (int i = 0; i < sample; i++) {
            int v = bytes[i] & 0xFF;
            if ((v < 0x09) || (v > 0x0D && v < 0x20)) {
                suspicious++;
            }
        }
        return suspicious > sample / 10;
    }

    public static String normalizeText(String text) {
        if (text == null) {
            return null;
        }
        return text.replace("\r\n", "\n")
                .replace('\r', '\n')
                .replaceAll("\\n{3,}", "\n\n")
                .replaceAll("[ \\t]+\\n", "\n")
                .trim();
    }
}
