/**
 * api/index.ts
 *
 * Axios-free HTTP client for Nexus backend.
 * - JWT Bearer token auto-attach
 * - 401 → auto-logout via auth store
 * - 202 → TOTP required (not an error)
 * - Base URL from VITE_API_BASE or relative /api
 */
import { useAuthStore } from '@/stores/auth'

const BASE_URL = import.meta.env.VITE_API_BASE || '/api'

/** Raw fetch wrapper with JSON + JWT */
export async function api<T = any>(
  path: string,
  opts: RequestInit & { params?: Record<string, any> } = {},
): Promise<T> {
  const { params, ...init } = opts

  // Build URL with query params
  let url = `${BASE_URL}${path}`
  if (params) {
    const sp = new URLSearchParams()
    for (const [k, v] of Object.entries(params)) {
      if (v !== undefined && v !== null) sp.set(k, String(v))
    }
    const qs = sp.toString()
    if (qs) url += `?${qs}`
  }

  // Attach JWT
  const auth = useAuthStore()
  const headers: Record<string, string> = {
    ...(init.headers as Record<string, string> || {}),
  }
  // Only set Content-Type for non-FormData bodies (FormData needs browser-generated boundary)
  if (!(init.body instanceof FormData)) {
    headers['Content-Type'] = 'application/json'
  }
  if (auth.token) {
    headers['Authorization'] = `Bearer ${auth.token}`
  }

  const res = await fetch(url, { ...init, headers })

  // 401 → force logout
  if (res.status === 401) {
    auth.forceLogout()
    throw new ApiError(401, '登录已过期，请重新登录')
  }

  // 202 → TOTP required (not an error, return the response)
  if (res.status === 202) {
    const text = await res.text()
    let data: any
    try { data = JSON.parse(text) } catch { data = { message: text } }
    throw new TotpRequiredError(data.detail || data.message || '请输入 TOTP 验证码')
  }

  // 429 → Account locked
  if (res.status === 429) {
    throw new ApiError(429, '登录尝试过多，账户已锁定 15 分钟')
  }

  // 204 No Content
  if (res.status === 204) return undefined as T

  // Try JSON
  const text = await res.text()
  let data: any
  try {
    data = JSON.parse(text)
  } catch {
    data = text
  }

  if (!res.ok) {
    const msg = data?.detail || data?.message || res.statusText
    throw new ApiError(res.status, msg)
  }

  return data as T
}

/** Convenience helpers */
export const http = {
  get: <T = any>(path: string, params?: Record<string, any>) =>
    api<T>(path, { method: 'GET', params }),

  post: <T = any>(path: string, body?: any) =>
    api<T>(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }),

  put: <T = any>(path: string, body?: any) =>
    api<T>(path, { method: 'PUT', body: body ? JSON.stringify(body) : undefined }),

  delete: <T = any>(path: string) =>
    api<T>(path, { method: 'DELETE' }),

  /** Upload FormData (multipart, auto Content-Type with boundary) */
  upload: <T = any>(path: string, formData: FormData) =>
    api<T>(path, { method: 'POST', body: formData }),
}

export class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message)
    this.name = 'ApiError'
  }
}

export class TotpRequiredError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'TotpRequiredError'
  }
}
