import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { apiClient } from "@/api/client";
import { useTeamStore } from "@/stores/team";
import { useDocumentsStore } from "@/stores/documents";
import { useCalendarStore } from "@/stores/calendar";
import { useChatSocket } from "@/stores/chatSocket";
import { useChatStream } from "@/stores/chatStream";
import type {
  IChatMessage,
  IConversation,
  WsClientMessage,
} from "@/api/types";

function generateId(): string {
  return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}

export const useChatStore = defineStore("chat", () => {
  // --- State (chatStore-owned) ---
  const conversations = ref<IConversation[]>([]);
  const currentConversationId = ref<string | null>(null);
  // Per-conversation in-flight tracking; isCurrentLoading derives from the
  // current conversation being in this set, so other tabs remain usable.
  const pendingConversations = ref<Set<string>>(new Set());
  const pendingLastUsedAt = ref<Map<string, number>>(new Map());
  let _is404Recovering = false;

  // --- Message helpers (chatStore-owned, shared with chatStream) ---
  function appendMessage(conversationId: string, message: IChatMessage): void {
    const conv = conversations.value.find((c) => c.id === conversationId);
    if (conv) {
      if (!Array.isArray(conv.messages)) conv.messages = [];
      conv.messages.push(message);
      conv.updated_at = new Date().toISOString();
    }
  }

  function updateMessage(
    conversationId: string,
    messageId: string,
    updates: Partial<IChatMessage>,
  ): void {
    const conv = conversations.value.find((c) => c.id === conversationId);
    if (!conv) return;
    const msg = conv.messages.find((m) => m.id === messageId);
    if (msg) Object.assign(msg, updates);
  }

  function markConversationPending(convId: string): void {
    pendingConversations.value = new Set(pendingConversations.value).add(convId);
    pendingLastUsedAt.value = new Map(pendingLastUsedAt.value).set(convId, Date.now());
  }

  function markConversationDone(convId: string): void {
    const next = new Set(pendingConversations.value); next.delete(convId);
    pendingConversations.value = next;
    const last = new Map(pendingLastUsedAt.value); last.delete(convId);
    pendingLastUsedAt.value = last;
  }

  // --- Lazy cross-store accessors (passed to chatStream as deps) ---
  let _teamStore: ReturnType<typeof useTeamStore> | null = null;
  let _calendarStore: ReturnType<typeof useCalendarStore> | null = null;
  const _getTeamStore = () => (_teamStore ??= useTeamStore());
  const _getCalendarStore = () => (_calendarStore ??= useCalendarStore());
  const _getDocumentsStore = () => useDocumentsStore();

  // --- chatStream: streaming state + dispatchWsEvent ---
  const stream = useChatStream({
    conversations,
    currentConversationId,
    appendMessage,
    updateMessage,
    markConversationDone,
    resolveIncomingConvId: () => socket.resolveIncomingConvId(),
    getTeamStore: _getTeamStore,
    getCalendarStore: _getCalendarStore,
    getDocumentsStore: _getDocumentsStore,
  });

  // --- chatSocket: WebSocket lifecycle + resolveIncomingConvId ---
  const socket = useChatSocket({
    currentConversationId,
    pendingConversations,
    pendingLastUsedAt,
    onMessage: (data) => stream.dispatch(data),
    onReconnect: () => _recoverTaskAfterReconnect(),
    onDisconnect: () => stream.clearAllStreamState(),
  });

  // --- Getters (chatStore-owned) ---
  const currentConversation = computed<IConversation | undefined>(() =>
    conversations.value.find((c) => c.id === currentConversationId.value),
  );
  const currentMessages = computed<IChatMessage[]>(
    () => currentConversation.value?.messages ?? [],
  );
  // `true` only when the current conversation is waiting on the agent.
  const isCurrentLoading = computed<boolean>(() => {
    const cid = currentConversationId.value;
    return !!cid && pendingConversations.value.has(cid);
  });

  // --- Actions ---

  /** Load all conversations from the server */
  async function loadConversations(): Promise<void> {
    try {
      const data = await apiClient.getConversations();
      conversations.value = data.map((conv: IConversation) => ({
        id: conv.id,
        title: conv.title || "对话",
        messages: Array.isArray(conv.messages) ? conv.messages : [],
        created_at: conv.created_at,
        updated_at: conv.updated_at,
      }));
    } catch (error) {
      console.error("Failed to load conversations:", error);
    }
  }

  /** Select a conversation by ID and load its messages */
  async function selectConversation(id: string, force = false): Promise<void> {
    currentConversationId.value = id;
    // P2 #10: 会话隔离 — 切换会话时重置 collaborationState，避免跨会话数据泄漏。
    stream.collaborationState.value = null;

    const conv = conversations.value.find((c) => c.id === id);
    // 本地临时会话尚未同步到服务端，跳过获取避免 404
    if (
      !conv?.is_local &&
      (force || !conv || !conv.messages || conv.messages.length === 0)
    ) {
      try {
        const fullConv = await apiClient.getConversation(id);
        if (conv) {
          conv.messages = fullConv.messages || [];
          // P0 #1 fix: never let the server's placeholder title ("对话")
          // overwrite a real title we already have locally.
          const serverTitle = fullConv.title || "";
          const localTitle = conv.title || "";
          const isServerPlaceholder =
            serverTitle === "对话" || serverTitle.trim() === "";
          const isLocalReal =
            localTitle && localTitle !== "新对话" && localTitle !== "对话";
          if (serverTitle && !isServerPlaceholder) conv.title = serverTitle;
          else if (!isLocalReal) conv.title = serverTitle || localTitle || "对话";
          conv.created_at = fullConv.created_at || conv.created_at;
          conv.updated_at = fullConv.updated_at || conv.updated_at;
        } else {
          // P1 #7 fix: If the conversation is not in the local list, add it.
          const serverTitle = fullConv.title || "新对话";
          conversations.value.unshift({
            id: fullConv.id || id,
            title: serverTitle === "对话" ? "新对话" : serverTitle,
            messages: fullConv.messages || [],
            created_at: fullConv.created_at || new Date().toISOString(),
            updated_at: fullConv.updated_at || new Date().toISOString(),
          });
        }
      } catch (error) {
        const isNotFound = (error as { status?: number })?.status === 404;
        if (isNotFound) {
          conversations.value = conversations.value.filter((c) => c.id !== id);
          stream.streamingStepsByConv.value.delete(id);
          pendingConversations.value.delete(id);
          pendingLastUsedAt.value.delete(id);
          if (currentConversationId.value === id) {
            currentConversationId.value = null;
            stream.boardState.value = null;
            stream.debateState.value = null;
            // 自动切换到下一个可用会话，没有则新建（防止级联 404）
            if (!_is404Recovering) {
              _is404Recovering = true;
              try {
                if (conversations.value.length > 0) {
                  await selectConversation(conversations.value[0].id);
                } else {
                  createConversation();
                }
              } finally {
                _is404Recovering = false;
              }
            }
          }
        } else {
          console.error("Failed to load conversation messages:", error);
        }
      }
    }

    // P2 #10: 恢复 collaborationState — 从会话消息中查找 collaboration_graph
    const restoredConv = conversations.value.find((c) => c.id === id);
    const graphMsg = restoredConv?.messages
      ? [...restoredConv.messages]
          .reverse()
          .find((m) => m.message_type === "collaboration_graph" && m.collaboration_graph)
      : undefined;
    if (graphMsg?.collaboration_graph) {
      stream.collaborationState.value = {
        contracts: [...graphMsg.collaboration_graph.contracts],
        notices: [...graphMsg.collaboration_graph.notices],
        reviews: [...graphMsg.collaboration_graph.reviews],
        risks: [...graphMsg.collaboration_graph.risks],
      };
    }
  }

  /** Create a new empty conversation */
  function createConversation(): void {
    const newConversation: IConversation = {
      id: generateId(),
      title: "新对话",
      messages: [],
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
      is_local: true,
    };
    conversations.value.unshift(newConversation);
    currentConversationId.value = newConversation.id;
    stream.clearConvSteps(newConversation.id);
  }

  /** Delete a conversation (server + local state) */
  async function deleteConversation(id: string): Promise<void> {
    const conv = conversations.value.find((c) => c.id === id);
    if (!conv?.is_local) {
      try {
        await apiClient.deleteConversation(id);
      } catch (error) {
        console.error("Failed to delete conversation:", error);
      }
    }
    conversations.value = conversations.value.filter((c) => c.id !== id);
    stream.streamingStepsByConv.value.delete(id);
    pendingConversations.value.delete(id);
    pendingLastUsedAt.value.delete(id);
    markConversationDone(id);
    if (currentConversationId.value === id) {
      currentConversationId.value = null;
      stream.boardState.value = null;
      stream.debateState.value = null;
      if (conversations.value.length > 0) {
        await selectConversation(conversations.value[0].id);
      } else {
        createConversation();
      }
    }
  }

  /** Append a user message + pending assistant placeholder; returns both. */
  function _appendUserAndAssistant(
    conversationId: string,
    content: string,
  ): { userId: string; assistantId: string } {
    const userId = generateId();
    appendMessage(conversationId, {
      id: userId,
      role: "user",
      content,
      timestamp: new Date().toISOString(),
    });
    const assistantId = generateId();
    appendMessage(conversationId, {
      id: assistantId,
      role: "assistant",
      content: "",
      timestamp: new Date().toISOString(),
      status: "pending",
    });
    return { userId, assistantId };
  }

  /** Send a message using REST API (fallback) */
  async function sendMessage(
    message: string,
    sources?: string[],
  ): Promise<void> {
    if (!currentConversationId.value) createConversation();
    const conversationId = currentConversationId.value as string;
    const { assistantId } = _appendUserAndAssistant(conversationId, message);
    markConversationPending(conversationId);

    try {
      const response = await apiClient.chat({
        message,
        conversation_id: conversationId,
        sources,
      });
      updateMessage(conversationId, assistantId, {
        content: response.message,
        matched_skill: response.matched_skill,
        routing_method: response.routing_method,
        confidence: response.confidence,
        task_id: response.task_id,
        status: response.status,
      });
      const conv = conversations.value.find((c) => c.id === conversationId);
      if (conv) {
        conv.is_local = false;
        if (conv.messages.length <= 2) {
          conv.title =
            message.length > 20 ? `${message.substring(0, 20)}...` : message;
        }
      }
    } catch (error) {
      updateMessage(conversationId, assistantId, {
        content: `请求失败: ${error instanceof Error ? error.message : "未知错误"}`,
        status: "completed",
      });
    } finally {
      markConversationDone(conversationId);
    }
  }

  /** Send a message via WebSocket for streaming */
  async function sendWsMessage(
    message: string,
    sources?: string[],
    model?: string,
  ): Promise<void> {
    if (!currentConversationId.value) createConversation();

    // Check WebSocket state BEFORE creating messages to avoid duplicates
    if (!socket.ws.value || socket.ws.value.readyState !== WebSocket.OPEN) {
      await sendMessage(message, sources);
      return;
    }

    const conversationId = currentConversationId.value as string;
    const { userId, assistantId } = _appendUserAndAssistant(
      conversationId,
      message,
    );
    markConversationPending(conversationId);
    stream.clearConvSteps(conversationId);

    // Problem 7: catch send() exceptions (e.g. connection closed mid-send)
    try {
      socket.ws.value.send(
        JSON.stringify({
          type: "chat",
          message,
          sources,
          conversation_id: conversationId,
          model,
        } as WsClientMessage),
      );
    } catch (error) {
      console.error("WebSocket send failed, falling back to REST:", error);
      const conv = conversations.value.find((c) => c.id === conversationId);
      if (conv) {
        conv.messages = conv.messages.filter(
          (m) => m.id !== userId && m.id !== assistantId,
        );
      }
      markConversationDone(conversationId);
      await sendMessage(message, sources);
      return;
    }

    // Update conversation title from first user message
    const conv = conversations.value.find((c) => c.id === conversationId);
    if (conv && conv.title === "新对话") {
      conv.title =
        message.length > 20 ? `${message.substring(0, 20)}...` : message;
    }
  }

  /** Stop the in-flight generation by sending a `cancel` WS message. */
  function stopGeneration(): void {
    const cid = currentConversationId.value;
    if (!socket.ws.value || socket.ws.value.readyState !== WebSocket.OPEN) {
      if (cid) {
        markConversationDone(cid);
        stream.clearConvSteps(cid);
      }
      return;
    }
    try {
      const cancelMsg: WsClientMessage = { type: "cancel" };
      socket.ws.value.send(JSON.stringify(cancelMsg));
    } catch (error) {
      console.error("Failed to send cancel message:", error);
      if (cid) {
        markConversationDone(cid);
        stream.clearConvSteps(cid);
      }
    }
  }

  /** After WebSocket reconnects, check for running tasks and resume them. */
  async function _recoverTaskAfterReconnect(): Promise<void> {
    const cid = currentConversationId.value;
    if (!cid) return;
    try {
      // Problem 2: query both 'running' and 'pending' tasks.
      const [runningTasks, pendingTasks] = await Promise.all([
        apiClient.listTasks("running"),
        apiClient.listTasks("pending"),
      ]);
      const activeTask = [...runningTasks, ...pendingTasks].find(
        (t) => t.metadata?.conversation_id === cid,
      );
      const canResume =
        activeTask &&
        socket.ws.value &&
        socket.ws.value.readyState === WebSocket.OPEN;
      if (!canResume) {
        // No active task — force reload conversation messages.
        await selectConversation(cid, true);
        return;
      }
      // P1 #12 fix: Clear the last pending assistant message's accumulated
      // content before resuming (replay would duplicate it otherwise).
      const conv = conversations.value.find((c) => c.id === cid);
      const lastPending = conv
        ? [...conv.messages]
            .reverse()
            .find((m) => m.role === "assistant" && m.status === "pending")
        : undefined;
      if (lastPending) {
        updateMessage(cid, lastPending.id, {
          content: "",
          thinking: "",
          tool_calls: [],
        });
      }
      markConversationPending(cid);
      try {
        socket.ws.value?.send(
          JSON.stringify({
            type: "resume",
            task_id: activeTask!.task_id,
            conversation_id: cid,
          }),
        );
      } catch (error) {
        console.error("Failed to send resume message:", error);
        markConversationDone(cid);
      }
    } catch (error) {
      console.error("Failed to recover task after reconnect:", error);
    }
  }

  /** Resend the last user message in the current conversation */
  async function resendLastUserMessage(): Promise<void> {
    const conversationId = currentConversationId.value;
    if (!conversationId) return;
    if (pendingConversations.value.has(conversationId)) return;
    const conv = conversations.value.find((c) => c.id === conversationId);
    if (!conv) return;
    const lastUserMsg = [...conv.messages]
      .reverse()
      .find((m) => m.role === "user");
    if (!lastUserMsg) return;
    const content = lastUserMsg.content.trim();
    if (!content) return;
    await sendWsMessage(content);
  }

  return {
    conversations,
    currentConversationId,
    isWsConnected: socket.isWsConnected,
    ws: socket.ws,
    pendingConversations,
    // Stream-owned state (re-exported for component compat)
    streamingStepsByConv: stream.streamingStepsByConv,
    boardState: stream.boardState,
    debateState: stream.debateState,
    collaborationState: stream.collaborationState,
    currentPhase: stream.currentPhase,
    phaseViolations: stream.phaseViolations,
    isPlanExec: stream.isPlanExec,
    // Legacy aliases for backward compat
    isLoading: isCurrentLoading,
    streamingSteps: stream.currentStreamingSteps,
    currentConversation,
    currentMessages,
    isCurrentLoading,
    currentStreamingSteps: stream.currentStreamingSteps,
    isBoardMode: stream.isBoardMode,
    // Actions
    loadConversations,
    selectConversation,
    createConversation,
    deleteConversation,
    sendMessage,
    sendWsMessage,
    resendLastUserMessage,
    stopGeneration,
    connectWebSocket: socket.connectWebSocket,
    disconnectWebSocket: socket.disconnectWebSocket,
  };
});
