package com.gzzm.lobster.thread;

import com.gzzm.lobster.common.IdGenerator;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.MessageRole;
import com.gzzm.lobster.common.ThreadStatus;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.storage.ContentStore;
import com.gzzm.lobster.workspace.Workspace;
import com.gzzm.lobster.workspace.WorkspaceDao;
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;

/**
 * ThreadService —— Thread 与 Message 领域服务 / Thread + message domain service.
 *
 * <p>提供：创建 thread、追加消息、加载轻量 transcript（按需扩展大 payload）。
 * Provides: create thread, append message, load lightweight transcript.
 */
@Service
public class ThreadService {

    /** 消息外置阈值：8KB / Externalize threshold: 8KB. */
    private static final int CONTENT_EXTERNAL_THRESHOLD = 8192;

    @Inject
    private ThreadDao threadDao;

    @Inject
    private ThreadMessageDao messageDao;

    @Inject
    private WorkspaceDao workspaceDao;

    @Inject
    private ContentStore contentStore;

    /**
     * thunwind DAO 绑定到首次使用的线程，ToolExecutor / 线程池上直接用 @Inject 字段
     * 会踩 "dao is created in a thread and used in an anther thread"。每次从容器现拿
     * 即可——与 MemoryService / LlmRuntime 同款修复。
     */
    private ThreadDao threads() {
        try {
            ThreadDao d = Tools.getBean(ThreadDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* 容器不可用，回退到 @Inject 字段 */ }
        return threadDao;
    }

    private ThreadMessageDao messages() {
        try {
            ThreadMessageDao d = Tools.getBean(ThreadMessageDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* 容器不可用，回退到 @Inject 字段 */ }
        return messageDao;
    }

    private WorkspaceDao workspaces() {
        try {
            WorkspaceDao d = Tools.getBean(WorkspaceDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* 容器不可用，回退到 @Inject 字段 */ }
        return workspaceDao;
    }

    /** 创建 thread 并初始化 workspace / Create thread and initialize workspace. */
    public ThreadRoom createThread(UserContext user, String title) throws Exception {
        if (user == null) throw new LobsterException("thread.auth", "Unauthenticated");
        ThreadRoom t = new ThreadRoom();
        t.setThreadId(IdGenerator.threadId());
        t.setUserId(user.getUserId());
        t.setOrgId(user.getOrgId());
        t.setTitle(title == null || title.isEmpty() ? "新对话" : title);
        t.setType("WORKSPACE");
        t.setStatus(ThreadStatus.active);
        t.setCreateTime(new Date());
        t.setUpdateTime(new Date());
        t.setLastActivityAt(new Date());

        Workspace ws = new Workspace();
        ws.setWorkspaceId(IdGenerator.workspaceId());
        ws.setThreadId(t.getThreadId());
        ws.setUserId(user.getUserId());
        ws.setName(t.getTitle());
        ws.setType("DEFAULT");
        ws.setCreateTime(new Date());
        ws.setUpdateTime(new Date());
        t.setWorkspaceId(ws.getWorkspaceId());

        workspaces().save(ws);
        threads().save(t);
        return t;
    }

    /**
     * 取 thread 并校验归属 / Load thread with ownership check.
     */
    public ThreadRoom requireOwnedThread(UserContext user, String threadId) throws Exception {
        ThreadRoom t = threads().getThread(threadId);
        if (t == null) throw new LobsterException("thread.not_found", "Thread not found: " + threadId);
        if (!t.getUserId().equals(user.getUserId())) {
            throw new LobsterException("thread.forbidden", "Thread does not belong to current user");
        }
        return t;
    }

    public ThreadRoom tryLoad(String threadId) throws Exception {
        return threads().getThread(threadId);
    }

    public List<ThreadRoom> listByUser(String userId, int offset, int limit) throws Exception {
        return threads().listByUser(userId, offset, limit);
    }

    public long countByUser(String userId) throws Exception {
        Long n = threads().countByUser(userId);
        return n == null ? 0L : n;
    }

    /**
     * 重命名 thread（仅归属者可改）/ Rename a thread, owner-only.
     *
     * @return 最新的 ThreadRoom
     */
    public ThreadRoom rename(UserContext user, String threadId, String title) throws Exception {
        if (user == null) throw new LobsterException("thread.auth", "Unauthenticated");
        if (title == null || title.trim().isEmpty()) {
            throw new LobsterException("thread.invalid", "Title cannot be empty");
        }
        String trimmed = title.trim();
        if (trimmed.length() > 200) trimmed = trimmed.substring(0, 200);
        // requireOwnedThread 会校验存在性 + 归属
        ThreadRoom t = requireOwnedThread(user, threadId);
        int n = threads().renameOwned(trimmed, new Date(), threadId, user.getUserId());
        if (n <= 0) throw new LobsterException("thread.rename_failed", "No row updated");
        t.setTitle(trimmed);
        return t;
    }

    /**
     * 软删除 thread（仅归属者可删）/ Soft-delete a thread, owner-only.
     *
     * <p>置 {@code deleteTag=1}，列表查询自带过滤不再可见；历史 messages /
     * artifacts / runs 不级联清理（审计留档），需要后续再做过期 GC。
     *
     * @return 更新行数（0 表示未改动）
     */
    public int delete(UserContext user, String threadId) throws Exception {
        if (user == null) throw new LobsterException("thread.auth", "Unauthenticated");
        requireOwnedThread(user, threadId);  // 存在性 + 归属校验
        return threads().softDelete(new Date(), threadId, user.getUserId());
    }

    /**
     * 追加一条消息 / Append one message to the transcript.
     *
     * <p>超过外置阈值的内容自动外置到 ContentStore。
     * Contents above the threshold are externalized to ContentStore.
     */
    public ThreadMessage appendMessage(ThreadRoom thread, MessageRole role, String content,
                                       String source, String runId, String toolCallId,
                                       String toolName, String toolCallsJson) throws Exception {
        return appendMessage(thread, role, content, source, runId, toolCallId, toolName,
                toolCallsJson, null);
    }

    /**
     * 扩展 overload —— 多收一个 {@code reasoningContent} 参数（thinking-mode 模型的 assistant
     * 消息用）. 其它角色/场景传 null 即可.
     */
    public ThreadMessage appendMessage(ThreadRoom thread, MessageRole role, String content,
                                       String source, String runId, String toolCallId,
                                       String toolName, String toolCallsJson,
                                       String reasoningContent) throws Exception {
        return appendMessage(thread, role, content, source, runId, toolCallId, toolName,
                toolCallsJson, reasoningContent, null);
    }

    /**
     * 全参版 —— 多模态 user 消息要在 {@code attachmentsJson} 里持久化关联的 mediaIds /
     * resourceIds，重建 transcript 时按它读回图片二进制 → base64 → 进
     * {@link com.gzzm.lobster.llm.LobsterMessage#userWithImages}.
     *
     * <p>{@code attachmentsJson} 期望格式:
     * <pre>{ "imageMediaIds": ["res_xxx","res_yyy"] }</pre>
     * 不是图片的附件不进这里（走 prelude / workspace index），避免重复.
     */
    public ThreadMessage appendMessage(ThreadRoom thread, MessageRole role, String content,
                                       String source, String runId, String toolCallId,
                                       String toolName, String toolCallsJson,
                                       String reasoningContent, String attachmentsJson) throws Exception {
        ThreadMessage m = new ThreadMessage();
        m.setMessageId(IdGenerator.messageId());
        m.setThreadId(thread.getThreadId());
        m.setRole(role);
        m.setSource(source);
        m.setRunId(runId);
        m.setToolCallId(toolCallId);
        m.setToolName(toolName);
        m.setToolCallsJson(toolCallsJson);
        if (reasoningContent != null && !reasoningContent.isEmpty()) {
            m.setReasoningContent(reasoningContent);
        }
        if (attachmentsJson != null && !attachmentsJson.isEmpty()) {
            m.setAttachmentsJson(attachmentsJson);
        }
        Long lastSeq = messages().maxSeq(thread.getThreadId());
        m.setSeq((lastSeq == null ? 0L : lastSeq) + 1);
        m.setCreateTime(new Date());

        if (content != null && content.length() > CONTENT_EXTERNAL_THRESHOLD) {
            try {
                String ref = contentStore.write("message", thread.getUserId(), content, "txt");
                m.setContent(content.substring(0, 512) + "..."); // 保留缩略 / inline preview
                m.setContentRef(ref);
            } catch (Throwable t) {
                try { Tools.log("[ThreadService] externalize content failed, falling back", t); }
                catch (Throwable ignore) { /* ignore */ }
                m.setContent(content);
            }
        } else {
            m.setContent(content);
        }
        messages().save(m);
        threads().touch(new Date(), thread.getThreadId());
        return m;
    }

    /**
     * 加载轻量 transcript 视图 / Load a lightweight transcript view.
     *
     * <p>大内容保留占位 / contentRef，不读取正文；发送视图层按需拉取。
     */
    public List<ThreadMessage> loadLightweightTranscript(String threadId) throws Exception {
        return messages().listByThread(threadId);
    }

    /** 读取大 payload 正文（按需）/ Lazily resolve the full content for a message. */
    public String resolveFullContent(ThreadMessage message) {
        if (message.getContentRef() == null) return message.getContent();
        try {
            String full = contentStore.read(message.getContentRef());
            return full == null ? message.getContent() : full;
        } catch (Throwable t) {
            return message.getContent();
        }
    }
}
