import { apiFetch } from "@/lib/apiFetch";
import { config } from "@config/config";

export type AgentModel = string;

export type PermissionReply = "once" | "always" | "reject";
export type AgentApprovalMode = "request" | "always";

export type AgentQuestionStatus =
  | "pending"
  | "submitting"
  | "answered"
  | "rejected"
  | "error";

export type AgentQuestionRequest = {
  requestId: string;
  sessionId: string;
  questions: Array<{
    header: string;
    question: string;
    options: Array<{
      label: string;
      description: string;
    }>;
    multiple?: boolean;
    custom?: boolean;
  }>;
  tool?: {
    messageID: string;
    callID: string;
  };
  createdAt: number;
  repliedAt?: number;
  status: AgentQuestionStatus;
  answers?: string[][];
  error?: string;
};

export type AgentTodoItem = {
  id: string;
  content: string;
  status: "pending" | "in_progress" | "completed" | "cancelled";
  priority?: "low" | "medium" | "high";
  createdAt?: number;
  updatedAt?: number;
};

export type AgentTodoUpdate = {
  sessionId: string;
  messageId?: string;
  todos: AgentTodoItem[];
  createdAt: number;
};

export type StreamEvent =
  | {
      type: "state";
      sessionId: string;
      messages: unknown[];
      isStreaming: boolean;
      runStatus?: string;
    }
  | { type: "token"; sessionId: string; content: string }
  | { type: "done"; sessionId: string; totalDurationMs?: number }
  | { type: "session_title"; sessionId: string; title: string }
  | {
      type: "progress";
      sessionId: string;
      id: string;
      phase: string;
      status: "running" | "completed" | "error";
      title: string;
      detail?: string;
      startedAt?: number;
      endedAt?: number;
      elapsedMs?: number;
      durationMs?: number;
    }
  | {
      type: "error";
      sessionId?: string;
      message: string;
      detail?: string;
      totalDurationMs?: number;
    }
  | {
      type: "auth_required";
      sessionId?: string;
      reason?: string;
      message: string;
    }
  | {
      type: "tool_call";
      sessionId: string;
      tool: string;
      params: Record<string, unknown>;
    }
  | {
      type: "permission_request";
      sessionId: string;
      requestId: string;
      permission: string;
      patterns: string[];
      target?: string;
      always: string[];
      tool?: {
        messageID: string;
        callID: string;
      };
      createdAt: number;
    }
  | {
      type: "permission_response";
      sessionId: string;
      requestId: string;
      reply: PermissionReply;
    }
  | {
      type: "question_request";
      sessionId: string;
      requestId: string;
      questions: AgentQuestionRequest["questions"];
      tool?: AgentQuestionRequest["tool"];
      createdAt: number;
    }
  | {
      type: "question_response";
      sessionId: string;
      requestId: string;
      answers?: string[][];
      rejected?: boolean;
    }
  | {
      type: "todo_update";
      sessionId: string;
      messageId?: string;
      todos: AgentTodoItem[];
      createdAt: number;
    };

type StreamOptions = {
  message: string;
  sessionId?: string;
  model?: AgentModel;
  approvalMode?: AgentApprovalMode;
  signal?: AbortSignal;
  onEvent: (event: StreamEvent) => void;
};

type ResumeStreamOptions = {
  sessionId: string;
  signal?: AbortSignal;
  onEvent: (event: StreamEvent) => void;
};

const parseEventBlock = (block: string): { event?: string; data?: string } => {
  const lines = block.split("\n");
  let event: string | undefined;
  const dataLines: string[] = [];

  for (const line of lines) {
    if (line.startsWith("event:")) {
      event = line.slice("event:".length).trim();
    } else if (line.startsWith("data:")) {
      dataLines.push(line.slice("data:".length).trim());
    }
  }

  return {
    event,
    data: dataLines.length ? dataLines.join("\n") : undefined,
  };
};

const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
  typeof value === "object" && value !== null && !Array.isArray(value);

const resolveToolParams = (
  params: unknown,
  argumentsPayload: unknown,
): Record<string, unknown> => {
  if (isObjectRecord(params) && Object.keys(params).length > 0) {
    return params;
  }
  if (isObjectRecord(argumentsPayload)) {
    return argumentsPayload;
  }
  if (typeof argumentsPayload === "string") {
    try {
      const parsed = JSON.parse(argumentsPayload) as unknown;
      return isObjectRecord(parsed) ? parsed : {};
    } catch {
      return {};
    }
  }
  return isObjectRecord(params) ? params : {};
};

const normalizeQuestionList = (value: unknown): AgentQuestionRequest["questions"] => {
  if (!Array.isArray(value)) return [];
  return value
    .filter(isObjectRecord)
    .map((question) => ({
      header: typeof question.header === "string" ? question.header : "",
      question: typeof question.question === "string" ? question.question : "",
      options: Array.isArray(question.options)
        ? question.options.filter(isObjectRecord).map((option) => ({
            label: typeof option.label === "string" ? option.label : "",
            description:
              typeof option.description === "string" ? option.description : "",
          }))
        : [],
      multiple: typeof question.multiple === "boolean" ? question.multiple : undefined,
      custom: typeof question.custom === "boolean" ? question.custom : undefined,
    }));
};

const normalizeAnswers = (value: unknown): string[][] | undefined => {
  if (!Array.isArray(value)) return undefined;
  return value.map((answer) =>
    Array.isArray(answer)
      ? answer.filter((item): item is string => typeof item === "string")
      : [],
  );
};

const normalizeQuestionTool = (value: unknown): AgentQuestionRequest["tool"] => {
  if (!isObjectRecord(value)) return undefined;
  const messageID =
    typeof value.messageID === "string"
      ? value.messageID
      : typeof value.message_id === "string"
        ? value.message_id
        : undefined;
  const callID =
    typeof value.callID === "string"
      ? value.callID
      : typeof value.call_id === "string"
        ? value.call_id
        : undefined;
  return messageID && callID ? { messageID, callID } : undefined;
};

const normalizeTodoStatus = (value: unknown): AgentTodoItem["status"] => {
  if (value === "in_progress" || value === "completed" || value === "cancelled") {
    return value;
  }
  return "pending";
};

const normalizeTodoPriority = (value: unknown): AgentTodoItem["priority"] => {
  if (value === "low" || value === "medium" || value === "high") {
    return value;
  }
  return undefined;
};

const normalizeTodos = (value: unknown): AgentTodoItem[] => {
  if (!Array.isArray(value)) return [];
  return value.filter(isObjectRecord).map((todo, index) => ({
    id:
      typeof todo.id === "string" && todo.id.trim()
        ? todo.id
        : `todo-${index}`,
    content: typeof todo.content === "string" ? todo.content : "",
    status: normalizeTodoStatus(todo.status),
    priority: normalizeTodoPriority(todo.priority),
    createdAt: typeof todo.created_at === "number" ? todo.created_at : undefined,
    updatedAt: typeof todo.updated_at === "number" ? todo.updated_at : undefined,
  }));
};

const emitParsedStreamEvent = (
  event: string,
  data: string,
  onEvent: (event: StreamEvent) => void,
) => {
  try {
    const parsed = JSON.parse(data) as {
      session_id?: string;
      content?: string;
      message?: string;
      detail?: string;
      tool?: unknown;
      params?: Record<string, unknown>;
      arguments?: unknown;
      id?: string;
      phase?: string;
      status?: "running" | "completed" | "error";
      title?: string;
      messages?: unknown[];
      is_streaming?: boolean;
      run_status?: string;
      started_at?: number;
      ended_at?: number;
      elapsed_ms?: number;
      duration_ms?: number;
      total_duration_ms?: number;
      request_id?: string;
      permission?: string;
      patterns?: unknown;
      target?: string;
      always?: unknown;
      created_at?: number;
      reply?: PermissionReply;
      questions?: unknown;
      answers?: unknown;
      rejected?: boolean;
      message_id?: string;
      todos?: unknown;
      reason?: string;
    };
    if (event === "state") {
      onEvent({
        type: "state",
        sessionId: parsed.session_id ?? "",
        messages: Array.isArray(parsed.messages) ? parsed.messages : [],
        isStreaming: parsed.is_streaming ?? false,
        runStatus: parsed.run_status,
      });
    } else if (event === "token") {
      onEvent({
        type: "token",
        sessionId: parsed.session_id ?? "",
        content: parsed.content ?? "",
      });
    } else if (event === "progress") {
      onEvent({
        type: "progress",
        sessionId: parsed.session_id ?? "",
        id: parsed.id ?? `${parsed.phase ?? "progress"}-${Date.now()}`,
        phase: parsed.phase ?? "progress",
        status: parsed.status ?? "running",
        title: parsed.title ?? "正在处理",
        detail: parsed.detail,
        startedAt: parsed.started_at,
        endedAt: parsed.ended_at,
        elapsedMs: parsed.elapsed_ms,
        durationMs: parsed.duration_ms,
      });
    } else if (event === "done") {
      onEvent({
        type: "done",
        sessionId: parsed.session_id ?? "",
        totalDurationMs: parsed.total_duration_ms,
      });
    } else if (event === "session_title") {
      onEvent({
        type: "session_title",
        sessionId: parsed.session_id ?? "",
        title: typeof parsed.title === "string" ? parsed.title : "",
      });
    } else if (event === "error") {
      onEvent({
        type: "error",
        sessionId: parsed.session_id,
        message: parsed.message ?? "unknown error",
        detail: parsed.detail,
        totalDurationMs: parsed.total_duration_ms,
      });
    } else if (event === "auth_required") {
      onEvent({
        type: "auth_required",
        sessionId: parsed.session_id,
        reason: parsed.reason,
        message: parsed.message ?? "登录态已过期，请刷新登录后重试",
      });
    } else if (event === "tool_call") {
      onEvent({
        type: "tool_call",
        sessionId: parsed.session_id ?? "",
        tool: typeof parsed.tool === "string" ? parsed.tool : "",
        params: resolveToolParams(parsed.params, parsed.arguments),
      });
    } else if (event === "permission_request") {
      onEvent({
        type: "permission_request",
        sessionId: parsed.session_id ?? "",
        requestId: parsed.request_id ?? "",
        permission: parsed.permission ?? "",
        patterns: Array.isArray(parsed.patterns)
          ? parsed.patterns.filter((item): item is string => typeof item === "string")
          : [],
        target: typeof parsed.target === "string" ? parsed.target : undefined,
        always: Array.isArray(parsed.always)
          ? parsed.always.filter((item): item is string => typeof item === "string")
          : [],
        tool: isObjectRecord(parsed.tool) &&
          typeof parsed.tool.messageID === "string" &&
          typeof parsed.tool.callID === "string"
          ? {
              messageID: parsed.tool.messageID,
              callID: parsed.tool.callID,
            }
          : undefined,
        createdAt: parsed.created_at ?? Date.now(),
      });
    } else if (event === "permission_response") {
      onEvent({
        type: "permission_response",
        sessionId: parsed.session_id ?? "",
        requestId: parsed.request_id ?? "",
        reply: parsed.reply ?? "reject",
      });
    } else if (event === "question_request") {
      onEvent({
        type: "question_request",
        sessionId: parsed.session_id ?? "",
        requestId: parsed.request_id ?? "",
        questions: normalizeQuestionList(parsed.questions),
        tool: normalizeQuestionTool(parsed.tool),
        createdAt: parsed.created_at ?? Date.now(),
      });
    } else if (event === "question_response") {
      onEvent({
        type: "question_response",
        sessionId: parsed.session_id ?? "",
        requestId: parsed.request_id ?? "",
        answers: normalizeAnswers(parsed.answers),
        rejected: parsed.rejected === true,
      });
    } else if (event === "todo_update") {
      onEvent({
        type: "todo_update",
        sessionId: parsed.session_id ?? "",
        messageId: parsed.message_id,
        todos: normalizeTodos(parsed.todos),
        createdAt: parsed.created_at ?? Date.now(),
      });
    }
  } catch {
    onEvent({
      type: "error",
      message: "invalid SSE data payload",
      detail: data,
    });
  }
};

const readStreamEvents = async (
  response: Response,
  onEvent: (event: StreamEvent) => void,
) => {
  if (!response.body) {
    return;
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder("utf-8");
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const blocks = buffer.split("\n\n");
    buffer = blocks.pop() ?? "";

    for (const block of blocks) {
      const { event, data } = parseEventBlock(block);
      if (!event || !data) continue;
      emitParsedStreamEvent(event, data, onEvent);
    }
  }
};

const ensureAgentSession = async (sessionId?: string) => {
  if (sessionId) return sessionId;

  const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
    projectHeaderMode: "include",
    skipAuthRedirect: true,
  });
  if (!response.ok) {
    throw new Error((await response.text()) || `session creation failed: ${response.status}`);
  }
  const payload = (await response.json()) as { session_id?: string };
  if (!payload.session_id) {
    throw new Error("session creation returned no session_id");
  }
  return payload.session_id;
};

export const streamAgentChat = async ({
  message,
  sessionId,
  model,
  approvalMode,
  signal,
  onEvent,
}: StreamOptions) => {
  let response: Response;
  try {
    const effectiveSessionId = await ensureAgentSession(sessionId);
    response = await apiFetch(
      `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(effectiveSessionId)}/runs`,
      {
        method: "POST",
        signal,
        headers: {
          "Content-Type": "application/json",
          Accept: "text/event-stream",
        },
        body: JSON.stringify({
          message,
          model,
          approval_mode: approvalMode,
        }),
        projectHeaderMode: "include",
        skipAuthRedirect: true,
      },
    );
  } catch (error) {
    const detail = error instanceof Error ? error.message : String(error);
    onEvent({
      type: "error",
      message: "network request failed",
      detail,
    });
    return;
  }

  if (!response.ok || !response.body) {
    const detail = await response.text();
    let message = "stream request failed";

    if (response.status === 403) {
      message = "Permission denied. Please contact administrator.";
    } else if (response.status === 401) {
      message = "Login expired. Please sign in again.";
    }

    onEvent({
      type: "error",
      message,
      detail:
        response.status === 403 || response.status === 401 ? undefined : detail,
    });
    return;
  }

  await readStreamEvents(response, onEvent);
};

export const resumeAgentChatStream = async ({
  sessionId,
  signal,
  onEvent,
}: ResumeStreamOptions) => {
  let response: Response;
  try {
    response = await apiFetch(
      `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/runs/current/events`,
      {
        method: "GET",
        signal,
        headers: {
          Accept: "text/event-stream",
        },
        projectHeaderMode: "include",
        skipAuthRedirect: true,
      },
    );
  } catch (error) {
    const detail = error instanceof Error ? error.message : String(error);
    onEvent({
      type: "error",
      sessionId,
      message: "network request failed",
      detail,
    });
    return;
  }

  if (!response.ok || !response.body) {
    const detail = await response.text();
    onEvent({
      type: "error",
      sessionId,
      message: "stream request failed",
      detail,
    });
    return;
  }

  await readStreamEvents(response, onEvent);
};

export const abortAgentChat = async (sessionId?: string) => {
  if (!sessionId) {
    return;
  }

  const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/runs/current`, {
    method: "DELETE",
    projectHeaderMode: "include",
    skipAuthRedirect: true,
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `abort request failed: ${response.status}`);
  }
};

export const replyAgentPermission = async (
  sessionId: string,
  requestId: string,
  reply: PermissionReply,
) => {
  const response = await apiFetch(
    `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/permission-responses`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        request_id: requestId,
        reply,
      }),
      projectHeaderMode: "include",
      skipAuthRedirect: true,
    },
  );

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `permission reply failed: ${response.status}`);
  }
};

export const replyAgentQuestion = async (
  sessionId: string,
  requestId: string,
  answers: string[][],
) => {
  const response = await apiFetch(
    `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/question-responses`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        request_id: requestId,
        action: "reply",
        answers,
      }),
      projectHeaderMode: "include",
      skipAuthRedirect: true,
    },
  );

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `question reply failed: ${response.status}`);
  }
};

export const rejectAgentQuestion = async (
  sessionId: string,
  requestId: string,
) => {
  const response = await apiFetch(
    `${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId)}/question-responses`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        request_id: requestId,
        action: "reject",
      }),
      projectHeaderMode: "include",
      skipAuthRedirect: true,
    },
  );

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `question reject failed: ${response.status}`);
  }
};

export const forkAgentChat = async (sessionId: string | undefined, keepMessageCount: number) => {
  const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/sessions/${encodeURIComponent(sessionId ?? "")}/forks`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      keep_message_count: keepMessageCount,
    }),
    projectHeaderMode: "include",
    skipAuthRedirect: true,
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `fork request failed: ${response.status}`);
  }

  const payload = (await response.json()) as { session_id?: string };
  if (!payload.session_id) {
    throw new Error("fork request returned no session_id");
  }
  return payload.session_id;
};
