package com.agent.management.service;

import com.agent.management.model.entity.ChatMessage;
import com.agent.management.repository.ChatMessageRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * Agent 对话消息 Service。
 *
 * <p>对应设计文档 {@code docs/design/agent-chat-page-design.md} §4.3.2。
 * 承载消息 CRUD、状态流转（{@code RUNNING/SUCCESS/FAILED}）与版本管理
 * （重新生成 / 编辑 / 切换版本）。</p>
 *
 * <p>版本管理核心：所有"重新生成/编辑"操作都是"创建新版本 + 旧版本置 inactive"，
 * 不修改原行；切换版本只是切换 {@code is_active} 标记。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatMessageService {

    private final ChatMessageRepository repo;

    /* ============== 状态流转 ============== */

    @Transactional
    public void markSuccess(Long messageId, String finalText) {
        repo.markSuccess(messageId, finalText, LocalDateTime.now());
    }

    @Transactional
    public void markFailed(Long messageId, String error) {
        repo.markFailed(messageId, error, LocalDateTime.now());
    }

    /* ============== 查询 ============== */

    public Optional<ChatMessage> findById(Long id) {
        return repo.findById(id);
    }

    public Optional<ChatMessage> findByRunId(String runId) {
        return repo.findByRunId(runId);
    }

    /** 当前激活分支的所有消息（按 createdAt 升序） */
    public List<ChatMessage> getActiveBranch(Long sessionId) {
        return repo.findActiveBranch(sessionId);
    }

    /** 同 parent 的所有兄弟消息（版本切换器用） */
    public List<ChatMessage> getVersions(Long parentId) {
        return repo.findByParentIdOrderByBranchOrderAsc(parentId);
    }

    /** 刷新页面后恢复 RUNNING 中的 assistant 消息（用于 SSE 流重连） */
    public Optional<ChatMessage> findRunningAssistant(Long sessionId) {
        return repo.findFirstBySessionIdAndRoleAndStatusAndIsActiveTrueOrderByCreatedAtDesc(
                sessionId, "ASSISTANT", "RUNNING");
    }

    /* ============== 版本管理 ============== */

    /**
     * 重新生成前置准备：把同 parent 的旧消息（含 old）置 inactive。
     *
     * <p>调用方紧接着应调 {@code ChatRunService.startRun}，并以该 parent（USER 消息）
     * 作为 {@code parentMessageId} 传入：startRun 检测 parentIsUser=true 后会复用该
     * USER 消息、新建 ASSISTANT 占位并触发 run。</p>
     *
     * @param parentId 同 parent 的所有消息将被置 inactive
     */
    @Transactional
    public void deactivateSiblings(Long parentId) {
        repo.deactivateSiblings(parentId);
    }

    /**
     * 编辑 user 消息：把旧 user 消息置 inactive，创建同 parent 的新 user 消息。
     *
     * <p>注意：此方法不触发 run，仅完成版本拆分。调用方应紧接着调
     * {@code ChatRunService.startRun}。</p>
     *
     * @return 新创建的 user 消息 id
     */
    @Transactional
    public Long editUserMessage(Long oldMessageId, String newContent) {
        ChatMessage old = repo.findById(oldMessageId)
                .orElseThrow(() -> new IllegalArgumentException("消息不存在: " + oldMessageId));
        if (!"USER".equals(old.getRole())) {
            throw new IllegalStateException("仅 USER 消息可编辑: " + oldMessageId);
        }
        repo.deactivateSiblings(old.getParentId());
        int newOrder = Optional.ofNullable(repo.maxBranchOrderByParent(old.getParentId())).orElse(-1) + 1;
        ChatMessage fresh = new ChatMessage();
        fresh.setSessionId(old.getSessionId());
        fresh.setParentId(old.getParentId());
        fresh.setIsActive(true);
        fresh.setBranchOrder(newOrder);
        fresh.setRole("USER");
        fresh.setContent(newContent);
        fresh.setStatus("SENT");
        fresh.setModelId(old.getModelId());
        fresh.setSkillFolder(old.getSkillFolder());
        repo.save(fresh);
        return fresh.getId();
    }

    /**
     * 切换激活版本：把"root → target"主路径上的消息全部激活，其他消息全部 inactive。
     *
     * <p>历史实现只 {@code deactivateSiblings(target.parentId)}，遗漏了 target 兄弟节点
     * 的<strong>子树</strong>——例如切换 assistant 版本时，旧版本的后续 USER/ASSISTANT
     * 消息仍是 active，{@code findActiveBranch} 会返回新旧两条分支的混合数据，
     * 前端展示直接错乱（即"切几次后对话不显示"的根因）。</p>
     *
     * <p>正确语义：版本切换 = 重新选定"主链路" = 只有 root → target 路径上的消息可见。
     * target 的子消息（用户曾经在 target 之后继续聊过的链路）按 ChatGPT 惯例也一并隐藏，
     * 等用户重新输入后才走新链路。</p>
     */
    @Transactional
    public void activateVersion(Long messageId) {
        ChatMessage target = repo.findById(messageId)
                .orElseThrow(() -> new IllegalArgumentException("消息不存在: " + messageId));
        Long sessionId = target.getSessionId();

        // 1. 沿 parentId 向上，收集 target → root 的祖先路径（含 target 自身）
        List<Long> pathIds = new ArrayList<>();
        Set<Long> visited = new HashSet<>();  // 防御脏数据导致的环
        ChatMessage cur = target;
        while (cur != null && visited.add(cur.getId())) {
            pathIds.add(cur.getId());
            Long pid = cur.getParentId();
            if (pid == null) break;
            cur = repo.findById(pid).orElse(null);
        }

        // 2. 全 session 先全部 inactive（覆盖旧分支残留 active 子树的脏状态）
        repo.deactivateAllBySession(sessionId);
        // 3. 只激活主路径上的消息
        repo.activateByIds(pathIds);
    }

    /**
     * 重置会话：把指定会话所有消息置 inactive（保留作为历史可见）。
     * 不删除数据，下次 user 发消息时视为新对话起点。
     */
    @Transactional
    public int deactivateAllInSession(Long sessionId) {
        return repo.deactivateAllBySession(sessionId);
    }
}

