import type {
  ApiResponse,
  CaptchaResult,
  ChatMessage,
  ChatSession,
  FinishTopicResult,
  PracticeReviewResult,
  LoginResult,
  PeriodicReport,
  ShareDraft,
  TeacherHelpCard,
  UserProfile,
  VoiceInputConfig,
  VoiceTranscriptionResult,
} from "../types/api";

const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "/api";
const TOKEN_KEY = "ai-kb-user-token";
const REQUEST_TIMEOUT_MS = 15_000;
const CONTENT_GENERATION_TIMEOUT_MS = 45_000;

interface RequestOptions {
  timeoutMs?: number;
}

export class ApiError extends Error {
  constructor(
    message: string,
    readonly status: number,
    readonly requestId = "",
  ) {
    super(message);
    this.name = "ApiError";
  }
}

export function getToken() {
  return window.localStorage.getItem(TOKEN_KEY);
}

export function saveToken(token: string) {
  window.localStorage.setItem(TOKEN_KEY, token);
}

export function clearToken() {
  window.localStorage.removeItem(TOKEN_KEY);
}

async function request<T>(path: string, init: RequestInit = {}, options: RequestOptions = {}): Promise<T> {
  const headers = new Headers(init.headers);
  headers.set("Content-Type", "application/json");
  const token = getToken();
  if (token) {
    headers.set("Authorization", `Bearer ${token}`);
  }
  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), options.timeoutMs ?? REQUEST_TIMEOUT_MS);
  let response: Response;
  try {
    response = await fetch(`${API_BASE}${path}`, { ...init, headers, signal: controller.signal });
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") {
      throw new ApiError("请求超时，请检查网络后重试", 408);
    }
    throw new ApiError("网络连接失败，请稍后重试", 0);
  } finally {
    window.clearTimeout(timeout);
  }

  const body = (await response.json().catch(() => ({ code: response.status, message: "服务响应异常", data: null }))) as ApiResponse<T>;
  if (!response.ok || body.code !== 0) {
    throw new ApiError(
      body.message || `${response.status} ${response.statusText}`,
      response.status,
      response.headers.get("X-Request-ID") ?? "",
    );
  }
  return body.data;
}

export const api = {
  captcha: () => request<CaptchaResult>("/auth/captcha"),
  sendSms: (phone: string, captchaId?: string, captchaCode?: string) =>
    request<null>("/auth/sms/send", {
      method: "POST",
      body: JSON.stringify({ phone, ...(captchaId && captchaCode ? { captchaId, captchaCode } : {}) }),
    }),
  login: (phone: string, code: string) =>
    request<LoginResult>("/auth/login", { method: "POST", body: JSON.stringify({ phone, code }) }),
  exchangeSso: (code: string) =>
    request<LoginResult>("/auth/sso/exchange", { method: "POST", body: JSON.stringify({ code }) }),
  logout: () => request<null>("/auth/logout", { method: "POST", body: JSON.stringify({}) }),
  profile: () => request<UserProfile>("/user/profile"),
  createSession: () => request<{ sessionId: number }>("/chat/session", { method: "POST", body: JSON.stringify({}) }),
  listSessions: () => request<ChatSession[]>("/chat/session/list"),
  history: (sessionId: number) => request<ChatMessage[]>(`/chat/history?sessionId=${sessionId}`),
  renameSession: (sessionId: number, title: string) =>
    request<ChatSession>("/chat/session/title", { method: "PUT", body: JSON.stringify({ sessionId, title }) }),
  deleteSession: (sessionId: number) => request<null>(`/chat/session/${sessionId}`, { method: "DELETE" }),
  finishTopic: (sessionId: number) => request<FinishTopicResult>(`/chat/session/${sessionId}/topic/finish`, { method: "POST", body: JSON.stringify({}) }),
  topicSettlement: (summaryId: number) => request<FinishTopicResult>(`/chat/topic/settlement/${summaryId}`),
  generateHelpCard: (sessionId: number) => request<TeacherHelpCard>(
    `/chat/session/${sessionId}/help-card`,
    { method: "POST", body: JSON.stringify({}) },
    { timeoutMs: CONTENT_GENERATION_TIMEOUT_MS },
  ),
  markHelpCardCopied: (cardId: number) => request<TeacherHelpCard>(`/chat/help-card/${cardId}/copied`, { method: "POST", body: JSON.stringify({}) }),
  helpCards: (limit = 20) => request<TeacherHelpCard[]>(`/chat/help-card/list?limit=${limit}`),
  deleteHelpCard: (cardId: number) => request<null>(`/chat/help-card/${cardId}`, { method: "DELETE" }),
  generateShareDraft: (sessionId: number) => request<ShareDraft>(
    `/chat/session/${sessionId}/share-draft`,
    { method: "POST", body: JSON.stringify({}) },
    { timeoutMs: CONTENT_GENERATION_TIMEOUT_MS },
  ),
  markShareDraftCopied: (draftId: number) => request<ShareDraft>(`/chat/share-draft/${draftId}/copied`, { method: "POST", body: JSON.stringify({}) }),
  shareDrafts: (limit = 20) => request<ShareDraft[]>(`/chat/share-draft/list?limit=${limit}`),
  deleteShareDraft: (draftId: number) => request<null>(`/chat/share-draft/${draftId}`, { method: "DELETE" }),
  practiceReview: () => request<PracticeReviewResult>("/user/growth-profile"),
  periodicReports: (limit = 10) => request<PeriodicReport[]>(`/user/periodic-report/list?limit=${limit}`),
  stop: (sessionId: number) => request<null>("/chat/stop", { method: "POST", body: JSON.stringify({ sessionId }) }),
  voiceConfig: () => request<VoiceInputConfig>("/voice/config"),
  submitFeedback: (messageId: number, content: string) => request<{ id: number }>("/feedback", { method: "POST", body: JSON.stringify({ messageId, content }) }),
};

export async function transcribeVoice(audio: Blob): Promise<VoiceTranscriptionResult> {
  const form = new FormData();
  form.append("audio", audio, "voice-recording");
  const headers = new Headers();
  const token = getToken();
  if (token) headers.set("Authorization", `Bearer ${token}`);
  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), 30_000);
  let response: Response;
  try {
    response = await fetch(`${API_BASE}/voice/transcribe`, { method: "POST", headers, body: form, signal: controller.signal });
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") throw new ApiError("语音识别超时，请稍后重试", 408);
    throw new ApiError("语音上传失败，请检查网络后重试", 0);
  } finally {
    window.clearTimeout(timeout);
  }
  const body = (await response.json().catch(() => ({ code: response.status, message: "服务响应异常", data: null }))) as ApiResponse<VoiceTranscriptionResult>;
  if (!response.ok || body.code !== 0) {
    throw new ApiError(body.message || "语音识别失败", response.status, response.headers.get("X-Request-ID") ?? "");
  }
  return body.data;
}

export async function streamChat(
  sessionId: number,
  message: string,
  onChunk: (chunk: string) => void,
  onReasoning?: (chunk: string) => void,
  onStatus?: (message: string, type: string, reasoningVisible: boolean) => void,
  signal?: AbortSignal,
  retry = false,
) {
  const token = getToken();
  const response = await fetch(`${API_BASE}/chat/completions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
    },
    body: JSON.stringify({ sessionId, message, retry }),
    signal,
  });
  if (!response.ok || !response.body) {
    const message = await readErrorMessage(response);
    throw new Error(message || "AI 回复连接失败");
  }
  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 parts = buffer.split(/\r?\n\r?\n/);
    buffer = parts.pop() ?? "";
    for (const part of parts) {
      const dataLines = part.split(/\r?\n/).filter((line) => line.startsWith("data:"));
      if (!dataLines.length) continue;
      const payload = dataLines.map((line) => line.slice(5).trimStart()).join("\n").trim();
      if (payload === "[DONE]") return;
      const parsed = JSON.parse(payload) as {
        type?: string;
        content?: string;
        message?: string;
        reasoningVisible?: boolean;
      };
      if (parsed.type === "queued" || parsed.type === "generating") {
        onStatus?.(parsed.message || "", parsed.type, Boolean(parsed.reasoningVisible));
        continue;
      }
      if (parsed.type === "error") {
        throw new Error(parsed.message || "AI 回复失败");
      }
      if (parsed.type === "reasoning" && parsed.content) onReasoning?.(parsed.content);
      if (parsed.type === "content" && parsed.content) onChunk(parsed.content);
    }
  }
}

async function readErrorMessage(response: Response) {
  try {
    const body = (await response.json()) as ApiResponse<unknown>;
    return body.message;
  } catch {
    return "";
  }
}
