package com.gzzm.lobster.workspace;

import com.gzzm.lobster.artifact.Artifact;
import com.gzzm.lobster.artifact.ArtifactService;
import com.gzzm.lobster.common.JsonUtil;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.MimeTypes;
import com.gzzm.lobster.common.ResourceSourceType;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.oa.OaFileClient;
import com.gzzm.lobster.oa.OaFileInfo;
import com.gzzm.lobster.storage.FileSystemContentStore;
import com.gzzm.platform.commons.Tools;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.util.Map;

/**
 * WorkspaceResourceResolver —— 把 workspace 引用解析为可入沙箱的字节流 /
 * Unified resolver that turns any workspace reference into ready-to-stage bytes.
 *
 * <p>接受 3 种入参形式并按前缀自动识别：
 * <ul>
 *   <li>{@code res_xxx}（{@link WorkspaceResource#getResourceId()}）→ 按 sourceType 派发</li>
 *   <li>{@code art_xxx}（{@link Artifact#getArtifactId()}）→ {@link ArtifactService#readArtifactBytes}</li>
 *   <li>{@code oa_xxx}（OA 文件 id）→ {@link OaFileClient#downloadBytes}</li>
 * </ul>
 *
 * <p>权限模型：Artifact / OA 文件必须属于 {@code user}；WorkspaceResource 必须挂在
 * 当前 {@code threadId} 下（或属于同一用户）.
 */
@Service
public class WorkspaceResourceResolver {

    @Inject
    private WorkspaceResourceDao resourceDao;

    @Inject
    private ArtifactService artifactService;

    @Inject
    private OaFileClient oaFileClient;

    @Inject
    private FileSystemContentStore contentStore;

    /** thunwind DAO 跨线程保护 */
    private WorkspaceResourceDao resourceDao() {
        try {
            WorkspaceResourceDao d = Tools.getBean(WorkspaceResourceDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* fallback */ }
        return resourceDao;
    }

    public ResolvedFile resolve(UserContext user, String threadId, String ref) throws Exception {
        if (ref == null || ref.isEmpty()) {
            throw new LobsterException("resolver.ref_empty", "ref is empty");
        }
        if (ref.startsWith("art_")) {
            return resolveArtifact(user, ref);
        }
        if (ref.startsWith("oa_")) {
            return resolveOaFile(user, ref);
        }
        // default: treat as resourceId
        return resolveByResourceId(user, threadId, ref);
    }

    private ResolvedFile resolveByResourceId(UserContext user, String threadId, String resourceId) throws Exception {
        WorkspaceResource r = resourceDao().getResource(resourceId);
        if (r == null) {
            throw new LobsterException("resolver.resource_not_found", "WorkspaceResource not found: " + resourceId);
        }
        if (user != null && r.getUserId() != null && !r.getUserId().equals(user.getUserId())) {
            // 放宽：只要挂在同一 thread 下也允许访问（跨用户协作场景极少，但允许 thread 归属链兜底）
            if (threadId == null || !threadId.equals(r.getThreadId())) {
                throw new LobsterException("resolver.forbidden", "Resource not owned: " + resourceId);
            }
        }
        ResourceSourceType st = r.getSourceType();
        if (st == ResourceSourceType.ARTIFACT || st == ResourceSourceType.WORKSHOP_DOC) {
            byte[] bytes = artifactService.readArtifactBytes(r.getSourceId());
            Artifact a = artifactService.get(r.getSourceId());
            String fmt = (a != null && a.getFormat() != null) ? a.getFormat() : extFromMime(r.getMimeType());
            return new ResolvedFile(bytes, r.getMimeType(), r.getDisplayName(), fmt);
        }
        if (st == ResourceSourceType.OA_FILE) {
            byte[] bytes = oaFileClient.downloadBytes(user, r.getSourceId());
            return new ResolvedFile(bytes, r.getMimeType(), r.getDisplayName(), extFromMime(r.getMimeType()));
        }
        if (st == ResourceSourceType.USER_UPLOAD) {
            // metadataJson 里 origRef = 上传原件在 ContentStore 的路径；mdRef = 解析后的 markdown.
            // 优先原件（LLM 想用 python-docx 处理 .docx 就需要原件字节）；
            // 退而求其次给 markdown（有 mdRef 没有 origRef 的情况：纯 txt/md 上传）.
            Map<String, Object> meta = parseMeta(r.getMetadataJson());
            String origRef = meta == null ? null : strOrNull(meta.get("origRef"));
            String mdRef = meta == null ? null : strOrNull(meta.get("mdRef"));
            String mime = r.getMimeType() != null ? r.getMimeType()
                    : (meta == null ? null : strOrNull(meta.get("mimeType")));
            if (origRef != null && !origRef.isEmpty()) {
                byte[] bytes = contentStore.readBinary(origRef);
                if (bytes == null) {
                    throw new LobsterException("resolver.upload_missing",
                            "USER_UPLOAD original content missing: " + origRef);
                }
                String ext = extFromMime(mime);
                if ("bin".equals(ext) && r.getDisplayName() != null && r.getDisplayName().contains(".")) {
                    ext = r.getDisplayName().substring(r.getDisplayName().lastIndexOf('.') + 1);
                }
                return new ResolvedFile(bytes, mime, r.getDisplayName(), ext);
            }
            if (mdRef != null && !mdRef.isEmpty()) {
                byte[] bytes = contentStore.readBinary(mdRef);
                if (bytes == null) {
                    throw new LobsterException("resolver.upload_missing",
                            "USER_UPLOAD markdown content missing: " + mdRef);
                }
                return new ResolvedFile(bytes, "text/markdown", r.getDisplayName(), "md");
            }
            throw new LobsterException("resolver.upload_no_content",
                    "USER_UPLOAD without origRef or mdRef: " + resourceId);
        }
        throw new LobsterException("resolver.unsupported",
                "unsupported sourceType: " + st + " for ref " + resourceId);
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> parseMeta(String json) {
        if (json == null || json.isEmpty()) return null;
        try { return JsonUtil.fromJson(json, Map.class); }
        catch (Throwable t) { return null; }
    }

    private static String strOrNull(Object o) {
        if (o == null) return null;
        String s = String.valueOf(o);
        return s.isEmpty() ? null : s;
    }

    private ResolvedFile resolveArtifact(UserContext user, String artifactId) throws Exception {
        Artifact a = artifactService.get(artifactId);
        if (a == null) {
            throw new LobsterException("resolver.artifact_not_found", "Artifact not found: " + artifactId);
        }
        if (user != null && a.getUserId() != null && !a.getUserId().equals(user.getUserId())) {
            throw new LobsterException("resolver.forbidden", "Artifact not owned: " + artifactId);
        }
        byte[] bytes = artifactService.readArtifactBytes(artifactId);
        String fmt = a.getFormat() == null ? "bin" : a.getFormat();
        return new ResolvedFile(bytes, mimeFromFormat(fmt), a.getTitle(), fmt);
    }

    private ResolvedFile resolveOaFile(UserContext user, String oaFileId) throws Exception {
        byte[] bytes = oaFileClient.downloadBytes(user, oaFileId);
        if (bytes == null) {
            throw new LobsterException("resolver.oa_not_found", "OA file not found or forbidden: " + oaFileId);
        }
        String mime = "application/octet-stream";
        String displayName = oaFileId;
        String ext = "bin";
        try {
            OaFileInfo info = oaFileClient.getMetadata(user, oaFileId);
            if (info != null) {
                mime = info.getMimeType() == null ? mime : info.getMimeType();
                displayName = info.getDisplayName() == null ? displayName : info.getDisplayName();
                ext = extFromMime(mime);
                if ("bin".equals(ext) && displayName.contains(".")) {
                    ext = displayName.substring(displayName.lastIndexOf('.') + 1);
                }
            }
        } catch (Throwable ignore) { /* metadata best-effort */ }
        return new ResolvedFile(bytes, mime, displayName, ext);
    }

    private static String extFromMime(String mime) { return MimeTypes.toExtension(mime); }
    private static String mimeFromFormat(String fmt) { return MimeTypes.fromFormat(fmt); }
}
