package com.gzzm.lobster.parse;

import com.gzzm.lobster.common.IdGenerator;
import com.gzzm.lobster.common.JsonUtil;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.storage.FileSystemContentStore;
import com.gzzm.lobster.thread.ThreadRoom;
import com.gzzm.lobster.workspace.WorkspaceResource;
import com.gzzm.lobster.workspace.WorkspaceService;
import com.gzzm.platform.commons.Tools;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

/** Orchestrates upload parsing and exposes the same parser path for other resource sources. */
@Service
public class UploadService {

    @Inject private FileSystemContentStore contentStore;
    @Inject private ParserRegistry parserRegistry;
    @Inject private WorkspaceService workspaceService;

    public UploadResult handle(ThreadRoom thread, UserContext user,
                               String originalName, String mimeType,
                               byte[] bytes) throws Exception {
        if (thread == null) throw new LobsterException("upload.no_thread", "thread missing");
        ParsedContent parsed = parseContent(user, originalName, mimeType, bytes, "upload");
        WorkspaceResource r = workspaceService.registerUpload(thread, user, parsed.originalName,
                parsed.uploadId, mimeType, parsed.metadataJson);

        UploadResult out = new UploadResult();
        out.resourceId = r.getResourceId();
        out.uploadId = parsed.uploadId;
        out.kind = parsed.kind;
        out.mdRef = parsed.mdRef;
        out.outlineRef = parsed.outlineRef;
        out.origRef = parsed.origRef;
        out.originalName = parsed.originalName;
        out.sizeBytes = parsed.sizeBytes;
        out.parseError = parsed.parseError;
        out.outlineSummary = parsed.outlineSummary;
        out.markdownPreview = parsed.mdRef == null ? null : previewMarkdown(parsed.mdRef);
        return out;
    }

    public ParsedContent parseContent(UserContext user, String originalName, String mimeType,
                                      byte[] bytes, String category) throws Exception {
        if (user == null) throw new LobsterException("upload.auth", "Unauthenticated");
        if (bytes == null || bytes.length == 0) {
            throw new LobsterException("upload.empty", "Empty upload");
        }
        long max = LobsterConfig.getUploadMaxBytes();
        if (bytes.length > max) {
            throw new LobsterException("upload.too_large",
                    "Upload exceeds " + max + " bytes: " + bytes.length);
        }

        String safeName = sanitizeName(originalName);
        String declaredExt = extractExt(safeName);
        String ext = declaredExt;
        String uploadId = IdGenerator.uploadId();

        String detectedExt = FileMagic.detectExt(bytes);
        if (detectedExt != null && !detectedExt.equals(declaredExt)) {
            ext = detectedExt;
            safeName = retagExtension(safeName, detectedExt);
            try {
                Tools.log("[UploadService] ext corrected by magic bytes: "
                        + declaredExt + " -> " + detectedExt + " (" + originalName + ")");
            } catch (Throwable ignore) { /* ignore */ }
        }

        String storeCategory = category == null || category.trim().isEmpty() ? "upload" : category;
        String origRef = contentStore.writeBinary(storeCategory, user.getUserId(), bytes,
                ext.isEmpty() ? "bin" : ext);

        DocumentParser parser = parserRegistry.pickForExtension(ext);
        String mdRef = null;
        String outlineRef = null;
        String kind;
        Outline outline = null;
        String parseError = null;

        if (parser == null) {
            kind = ext.isEmpty() ? "unknown" : ext;
            parseError = "unsupported extension: " + ext;
        } else {
            try (InputStream in = new java.io.ByteArrayInputStream(bytes)) {
                ParseResult pr = parser.parse(in, safeName, mimeType);
                kind = pr.getKind();
                outline = pr.getOutline();
                mdRef = contentStore.write(storeCategory, user.getUserId(),
                        pr.getMarkdown(), "md");
                String outlineJson = JsonUtil.toJson(outline);
                outlineRef = contentStore.write(storeCategory, user.getUserId(),
                        outlineJson, "outline.json");
            } catch (Throwable t) {
                kind = parser.kind();
                parseError = safeMsg(t);
                try { Tools.log("[UploadService] parse failed: " + safeName, t); } catch (Throwable ignore) { /* ignore */ }
            }
        }

        Map<String, Object> meta = new LinkedHashMap<String, Object>();
        meta.put("uploadId", uploadId);
        meta.put("originalName", safeName);
        meta.put("mimeType", mimeType);
        meta.put("sizeBytes", bytes.length);
        meta.put("kind", kind);
        meta.put("origRef", origRef);
        if (mdRef != null) meta.put("mdRef", mdRef);
        if (outlineRef != null) meta.put("outlineRef", outlineRef);
        if (parseError != null) meta.put("parseError", parseError);
        if (outline != null) meta.put("summary", outline.summarize(5));

        ParsedContent out = new ParsedContent();
        out.uploadId = uploadId;
        out.kind = kind;
        out.originalName = safeName;
        out.sizeBytes = bytes.length;
        out.origRef = origRef;
        out.mdRef = mdRef;
        out.outlineRef = outlineRef;
        out.parseError = parseError;
        out.outlineSummary = outline == null ? null : outline.summarize(5);
        out.metadataJson = JsonUtil.toJson(meta);
        return out;
    }

    private String previewMarkdown(String mdRef) {
        try {
            String full = contentStore.read(mdRef);
            if (full == null) return null;
            int cap = Math.min(full.length(), 1200);
            return full.substring(0, cap);
        } catch (Throwable t) {
            return null;
        }
    }

    private static String sanitizeName(String name) {
        if (name == null || name.isEmpty()) return "upload.bin";
        int sep = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
        String base = sep >= 0 ? name.substring(sep + 1) : name;
        return base.trim();
    }

    private static String extractExt(String name) {
        if (name == null) return "";
        int dot = name.lastIndexOf('.');
        if (dot < 0 || dot == name.length() - 1) return "";
        return name.substring(dot + 1).toLowerCase(Locale.ROOT);
    }

    private static String retagExtension(String name, String newExt) {
        if (name == null || name.isEmpty()) return "upload." + newExt;
        int dot = name.lastIndexOf('.');
        if (dot <= 0) return name + "." + newExt;
        return name.substring(0, dot + 1) + newExt;
    }

    private static String safeMsg(Throwable t) {
        String m = t.getMessage();
        return m == null ? t.getClass().getSimpleName() : m;
    }

    public static class UploadResult {
        public String resourceId;
        public String uploadId;
        public String kind;
        public String originalName;
        public int sizeBytes;
        public String origRef;
        public String mdRef;
        public String outlineRef;
        public String parseError;
        public Map<String, Object> outlineSummary;
        public String markdownPreview;
    }

    public static class ParsedContent {
        public String uploadId;
        public String kind;
        public String originalName;
        public int sizeBytes;
        public String origRef;
        public String mdRef;
        public String outlineRef;
        public String parseError;
        public Map<String, Object> outlineSummary;
        public String metadataJson;
    }
}
