package com.gzzm.lobster.workspace;

import com.gzzm.lobster.artifact.Artifact;
import com.gzzm.lobster.artifact.ArtifactService;
import com.gzzm.lobster.common.ArtifactType;
import com.gzzm.lobster.common.IdGenerator;
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.common.WorkspaceResourceStatus;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.oa.OaFileClient;
import com.gzzm.lobster.storage.FileSystemContentStore;
import com.gzzm.lobster.thread.ThreadRoom;
import com.gzzm.platform.commons.Tools;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * WorkspaceService —— 工作区资源领域服务 / Workspace resource service.
 *
 * <p>提供统一的 list / search / read / write 能力，作为 Layer 1 built-in tools
 * 的后端实现。默认不复制 OA 文件，仅通过 OA_FILE 资源引用持有。
 */
@Service
public class WorkspaceService {

    @Inject
    private WorkspaceDao workspaceDao;

    @Inject
    private WorkspaceResourceDao resourceDao;

    @Inject
    private ArtifactService artifactService;

    @Inject
    private FileSystemContentStore contentStore;

    @Inject
    private OaFileClient oaFileClient;

    /** thunwind DAO 跨线程保护，详见 feedback_thunwind_dao_thread_binding 记录 */
    private WorkspaceDao workspaceDao() {
        try {
            WorkspaceDao d = Tools.getBean(WorkspaceDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* fallback */ }
        return workspaceDao;
    }
    private WorkspaceResourceDao resourceDao() {
        try {
            WorkspaceResourceDao d = Tools.getBean(WorkspaceResourceDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* fallback */ }
        return resourceDao;
    }
    private OaFileClient oaFileClient() {
        try {
            OaFileClient c = Tools.getBean(OaFileClient.class);
            if (c != null) return c;
        } catch (Throwable ignore) { /* fallback */ }
        return oaFileClient;
    }

    public Workspace getOrCreate(ThreadRoom thread, UserContext user) throws Exception {
        Workspace ws = workspaceDao().getByThread(thread.getThreadId());
        if (ws != null) return ws;
        ws = new Workspace();
        ws.setWorkspaceId(IdGenerator.workspaceId());
        ws.setThreadId(thread.getThreadId());
        ws.setUserId(user.getUserId());
        ws.setName(thread.getTitle());
        ws.setType("DEFAULT");
        ws.setCreateTime(new Date());
        ws.setUpdateTime(new Date());
        workspaceDao().save(ws);
        return ws;
    }

    /** 列出当前 thread 的资源条目 / List resource entries for a thread. */
    public List<WorkspaceResource> listResources(String threadId, ResourceSourceType filter,
                                                 int offset, int limit) throws Exception {
        if (filter == null) {
            return resourceDao().listByThread(threadId, WorkspaceResourceStatus.ACTIVE, offset, limit);
        }
        return resourceDao().listByThreadAndType(threadId, filter, WorkspaceResourceStatus.ACTIVE, offset, limit);
    }

    public long countResources(String threadId) throws Exception {
        Long c = resourceDao().countByThread(threadId, WorkspaceResourceStatus.ACTIVE);
        return c == null ? 0L : c;
    }

    public List<WorkspaceResource> searchByTitle(String threadId, String query, int limit) throws Exception {
        String pattern = "%" + (query == null ? "" : query.trim()) + "%";
        return resourceDao().searchByTitle(threadId, pattern, WorkspaceResourceStatus.ACTIVE, limit);
    }

    /** 登记一条 OA 文件引用资源 / Register an OA file reference. */
    public WorkspaceResource registerOaFileReference(ThreadRoom thread, UserContext user,
                                                     String oaFileId, String displayName,
                                                     String mimeType, String origin) throws Exception {
        return registerOaFileReference(thread, user, oaFileId, displayName, mimeType, origin, null);
    }

    public WorkspaceResource registerOaFileReference(ThreadRoom thread, UserContext user,
                                                     String oaFileId, String displayName,
                                                     String mimeType, String origin,
                                                     String metadataJson) throws Exception {
        List<WorkspaceResource> existing = resourceDao().findByThreadAndSource(thread.getThreadId(),
                ResourceSourceType.OA_FILE, oaFileId, WorkspaceResourceStatus.ACTIVE);
        if (existing != null && !existing.isEmpty()) {
            WorkspaceResource r0 = existing.get(0);
            if (metadataJson != null && (r0.getMetadataJson() == null || r0.getMetadataJson().isEmpty())) {
                r0.setMetadataJson(metadataJson);
                r0.setUpdateTime(new Date());
                resourceDao().save(r0);
            }
            return r0;
        }
        Workspace ws = getOrCreate(thread, user);
        WorkspaceResource r = new WorkspaceResource();
        r.setResourceId(IdGenerator.resourceId());
        r.setWorkspaceId(ws.getWorkspaceId());
        r.setThreadId(thread.getThreadId());
        r.setUserId(user.getUserId());
        r.setSourceType(ResourceSourceType.OA_FILE);
        r.setSourceId(oaFileId);
        r.setDisplayName(displayName);
        r.setMimeType(mimeType);
        r.setOrigin(origin == null ? "user_reference" : origin);
        r.setMetadataJson(metadataJson);
        r.setStatus(WorkspaceResourceStatus.ACTIVE);
        r.setCreateTime(new Date());
        r.setUpdateTime(new Date());
        resourceDao().save(r);
        return r;
    }

    /**
     * 登记一条用户上传资源 / Register a user-upload resource.
     *
     * <p>{@code metadataJson} 按 {@code design-big-lobster-document-parsing} §2.3 约定存放：
     * {@code {origRef, mdRef, outlineRef, kind, mimeType, sizeBytes, originalName,
     *        summary:{title, kind, totalChars, sectionCount, stats, topSections}}}.
     */
    public WorkspaceResource registerUpload(ThreadRoom thread, UserContext user,
                                            String displayName, String uploadId,
                                            String mimeType, String metadataJson) throws Exception {
        Workspace ws = getOrCreate(thread, user);
        WorkspaceResource r = new WorkspaceResource();
        r.setResourceId(IdGenerator.resourceId());
        r.setWorkspaceId(ws.getWorkspaceId());
        r.setThreadId(thread.getThreadId());
        r.setUserId(user.getUserId());
        r.setSourceType(ResourceSourceType.USER_UPLOAD);
        r.setSourceId(uploadId);
        r.setDisplayName(displayName);
        r.setMimeType(mimeType);
        r.setOrigin("user_upload");
        r.setStatus(WorkspaceResourceStatus.ACTIVE);
        r.setMetadataJson(metadataJson);
        r.setCreateTime(new Date());
        r.setUpdateTime(new Date());
        resourceDao().save(r);
        return r;
    }

    /** 为 artifact 登记一条资源视图 / Register a workspace resource view for an artifact. */
    public WorkspaceResource registerArtifact(ThreadRoom thread, UserContext user, Artifact artifact) throws Exception {
        return registerArtifact(thread, user, artifact, null);
    }

    public WorkspaceResource registerArtifact(ThreadRoom thread, UserContext user, Artifact artifact,
                                              String metadataJson) throws Exception {
        Workspace ws = getOrCreate(thread, user);
        WorkspaceResource r = new WorkspaceResource();
        r.setResourceId(IdGenerator.resourceId());
        r.setWorkspaceId(ws.getWorkspaceId());
        r.setThreadId(thread.getThreadId());
        r.setUserId(user.getUserId());
        ResourceSourceType srcType = artifact.getArtifactType() == ArtifactType.WORKING_COPY
                ? ResourceSourceType.WORKSHOP_DOC : ResourceSourceType.ARTIFACT;
        r.setSourceType(srcType);
        r.setSourceId(artifact.getArtifactId());
        r.setDisplayName(artifact.getTitle());
        r.setMimeType(guessMime(artifact.getFormat()));
        r.setArtifactType(artifact.getArtifactType() == null ? null : artifact.getArtifactType().name());
        r.setOrigin("assistant_output");
        r.setMetadataJson(ResourceMetadata.writeArtifactLifecycleDefaults(metadataJson,
                artifact.getArtifactType() == null ? null : artifact.getArtifactType().name(),
                artifact.getTitle(), r.getMimeType()));
        r.setStatus(WorkspaceResourceStatus.ACTIVE);
        r.setCreateTime(new Date());
        r.setUpdateTime(new Date());
        resourceDao().save(r);
        return r;
    }

    private String guessMime(String format) {
        return MimeTypes.fromFormat(format);
    }

    /**
     * 按 resourceId 查一条资源（不检查归属，调用方自己校验；工具层一般走 readResource / 归属由 thread 归属链兜底）.
     */
    public WorkspaceResource getResource(String resourceId) throws Exception {
        return resourceDao().getResource(resourceId);
    }

    /**
     * 更新资源的 summary 字段（保留 metadataJson 其它字段）.
     *
     * @param resourceId 资源 id
     * @param userId     操作用户 id，只有归属者可改
     * @param summary    新 summary；空串/ null 表示清除
     * @param updatedBy  写入者标记（user / assistant / system）
     * @return 更新后的资源；找不到或无权限返回 null
     */
    public WorkspaceResource updateResourceSummary(String resourceId, String userId,
                                                   String summary, String updatedBy) throws Exception {
        if (resourceId == null || resourceId.isEmpty()) return null;
        WorkspaceResource r = resourceDao().getResource(resourceId);
        if (r == null) return null;
        if (userId != null && !userId.equals(r.getUserId())) return null;
        String newMeta = ResourceMetadata.writeSummary(r.getMetadataJson(),
                summary == null ? null : summary.trim(),
                updatedBy == null ? "assistant" : updatedBy,
                System.currentTimeMillis());
        r.setMetadataJson(newMeta);
        r.setUpdateTime(new Date());
        resourceDao().save(r);
        return r;
    }

    public WorkspaceResource updateArtifactLifecycle(String resourceId, String userId,
                                                     String lifecycleMetadataJson) throws Exception {
        if (resourceId == null || resourceId.isEmpty()) return null;
        WorkspaceResource r = resourceDao().getResource(resourceId);
        if (r == null) return null;
        if (userId != null && !userId.equals(r.getUserId())) return null;
        if (r.getSourceType() != ResourceSourceType.ARTIFACT
                && r.getSourceType() != ResourceSourceType.WORKSHOP_DOC) {
            return r;
        }
        r.setMetadataJson(ResourceMetadata.mergeArtifactLifecycle(r.getMetadataJson(), lifecycleMetadataJson));
        r.setUpdateTime(new Date());
        resourceDao().save(r);
        return r;
    }

    /** 按 resourceId 读取内容（统一读取入口）/ Unified read entry. */
    public String readResource(UserContext currentUser, String threadId, String resourceId, int offset, int limit) throws Exception {
        WorkspaceResource r = resourceDao().getResource(resourceId);
        if (r == null) throw new LobsterException("ws.resource.not_found", "Resource not found");
        if (currentUser == null) throw new LobsterException("ws.resource.auth", "Unauthenticated");
        if (!currentUser.getUserId().equals(r.getUserId())
                || threadId == null || !threadId.equals(r.getThreadId())) {
            throw new LobsterException("ws.resource.forbidden", "Resource not owned by current thread/user");
        }
        if (r.getSourceType() == ResourceSourceType.OA_FILE) {
            String mdRef = readMdRef(r);
            if (mdRef != null && !mdRef.isEmpty()) {
                if (limit <= 0) return contentStore.read(mdRef);
                return contentStore.read(mdRef, offset, limit);
            }
            String full = oaFileClient().readAsText(currentUser, r.getSourceId());
            if (full == null) throw new LobsterException("ws.oa_file.not_found", "OA file not found or forbidden");
            if (limit <= 0) return full;
            int start = Math.min(full.length(), Math.max(0, offset));
            int end = Math.min(full.length(), start + limit);
            return full.substring(start, end);
        }
        if (r.getSourceType() == ResourceSourceType.ARTIFACT
                || r.getSourceType() == ResourceSourceType.WORKSHOP_DOC) {
            return artifactService.readArtifactContent(r.getSourceId(), offset, limit);
        }
        if (r.getSourceType() == ResourceSourceType.USER_UPLOAD) {
            String mdRef = readMdRef(r);
            if (mdRef == null || mdRef.isEmpty()) {
                return "[USER_UPLOAD:" + r.getSourceId() + "] 原件未解析，无法按字符读取；"
                        + "原始文件仍保留在 ContentStore。";
            }
            if (limit <= 0) return contentStore.read(mdRef);
            return contentStore.read(mdRef, offset, limit);
        }
        return "[" + r.getSourceType() + ":" + r.getSourceId() + "] cannot be read as text";
    }

    /** 读 USER_UPLOAD 资源的 markdown contentRef，失败返回 null（调用方做降级）. */
    @SuppressWarnings("unchecked")
    private String readMdRef(WorkspaceResource r) {
        String mj = r.getMetadataJson();
        if (mj == null || mj.isEmpty()) return null;
        try {
            Map<String, Object> m = JsonUtil.fromJson(mj, Map.class);
            Object v = m == null ? null : m.get("mdRef");
            return v == null ? null : String.valueOf(v);
        } catch (Throwable t) {
            try { Tools.log("[WorkspaceService] parse metadataJson failed: " + r.getResourceId(), t); } catch (Throwable ignore) { /* ignore */ }
            return null;
        }
    }
}
