import type { OperationInput, PlatformOperation } from './types';
import { tokenStore, type TokenPair } from './token-store';

const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '');
let refreshPromise: Promise<TokenPair | null> | null = null;

export class ApiError extends Error {
  constructor(
    message: string,
    readonly status: number,
    readonly details?: unknown,
  ) {
    super(message);
  }
}

async function parseResponse(response: Response): Promise<unknown> {
  if (response.status === 204) return null;
  const contentType = response.headers.get('content-type') || '';
  if (contentType.includes('json')) return response.json();
  const text = await response.text();
  return text || null;
}

async function refreshTokens(): Promise<TokenPair | null> {
  const tokens = tokenStore.get();
  if (!tokens?.refreshToken) return null;
  const response = await fetch(`${apiBaseUrl}/api/auth/refresh`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refreshToken: tokens.refreshToken }),
  });
  if (!response.ok) {
    tokenStore.set(null);
    return null;
  }
  const next = (await response.json()) as TokenPair;
  tokenStore.set(next);
  return next;
}

async function authorizedFetch(url: string, init: RequestInit, retry = true): Promise<Response> {
  const accessToken = tokenStore.get()?.accessToken;
  const headers = new Headers(init.headers);
  if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);
  const response = await fetch(`${apiBaseUrl}${url}`, { ...init, headers });
  if (response.status !== 401 || !retry) return response;
  refreshPromise ??= refreshTokens().finally(() => { refreshPromise = null; });
  const refreshed = await refreshPromise;
  return refreshed ? authorizedFetch(url, init, false) : response;
}

function appendQuery(url: URL, query: Record<string, unknown>) {
  for (const [key, value] of Object.entries(query)) {
    if (value === undefined || value === null || value === '') continue;
    if (Array.isArray(value)) value.forEach((item) => url.searchParams.append(key, String(item)));
    else url.searchParams.set(key, String(value));
  }
}

export async function apiRequest<T = unknown>(
  operation: PlatformOperation,
  input: OperationInput = {},
  signal?: AbortSignal,
): Promise<T> {
  const pathValues = (input.path || {}) as Record<string, unknown>;
  let route = operation.path.replace(/\{([^}]+)\}/g, (_, name: string) => encodeURIComponent(String(pathValues[name] ?? '')));
  const url = new URL(route, window.location.origin);
  appendQuery(url, ((input.query || {}) as Record<string, unknown>));
  route = `${url.pathname}${url.search}`;
  const headers: Record<string, string> = {};
  const init: RequestInit = { method: operation.method, headers, signal };
  if (input.body !== undefined && operation.method !== 'GET') {
    headers['Content-Type'] = 'application/json';
    init.body = JSON.stringify(input.body);
  }
  const response = await authorizedFetch(route, init);
  const payload = await parseResponse(response);
  if (!response.ok) {
    const problem = payload as { detail?: string; title?: string } | null;
    throw new ApiError(problem?.detail || problem?.title || `请求失败（HTTP ${response.status}）`, response.status, payload);
  }
  return payload as T;
}

export async function authRequest<T>(path: string, body?: unknown): Promise<T> {
  const response = await fetch(`${apiBaseUrl}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const payload = await parseResponse(response);
  if (!response.ok) {
    const problem = payload as { detail?: string; title?: string } | null;
    throw new ApiError(problem?.detail || problem?.title || `请求失败（HTTP ${response.status}）`, response.status, payload);
  }
  return payload as T;
}

export async function getCurrentUser<T>(): Promise<T> {
  const response = await authorizedFetch('/api/me', { method: 'GET' });
  const payload = await parseResponse(response);
  if (!response.ok) throw new ApiError('登录会话已失效', response.status, payload);
  return payload as T;
}
