import { API_BASE_URL } from "@/shared/config/env.js";

const TOKEN_KEY = "hyapp-admin.access-token";
const APP_CODE_KEY = "hyapp-admin.app-code";
const APP_CODE_HEADER = "X-App-Code";
const NETWORK_RETRY_ATTEMPTS = 2;
const NETWORK_RETRY_DELAY_MS = 350;

let accessToken = window.localStorage.getItem(TOKEN_KEY) || "";
let selectedAppCode = normalizeAppCode(window.localStorage.getItem(APP_CODE_KEY) || "lalu");
let refreshHandler: (() => Promise<boolean>) | null = null;
let refreshInFlight: Promise<boolean> | null = null;
let unauthorizedHandler: (() => void) | null = null;

export type QueryValue = string | number | boolean | null | undefined;
export type QueryParams = Record<string, QueryValue>;
export type HttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";

export interface ApiRequestOptions<TBody = unknown> {
  body?: TBody;
  headers?: Record<string, string>;
  method?: HttpMethod;
  query?: QueryParams;
  raw?: boolean;
  retry?: boolean;
  skipAuth?: boolean;
}

interface ApiEnvelope<TData> {
  code?: number;
  data?: TData;
  message?: string;
}

export function getAccessToken() {
  return accessToken;
}

export function setAccessToken(token: string) {
  accessToken = token || "";
  if (accessToken) {
    window.localStorage.setItem(TOKEN_KEY, accessToken);
  } else {
    window.localStorage.removeItem(TOKEN_KEY);
  }
}

export function getSelectedAppCode() {
  return selectedAppCode;
}

export function setSelectedAppCode(appCode: string) {
  selectedAppCode = normalizeAppCode(appCode);
  if (selectedAppCode) {
    window.localStorage.setItem(APP_CODE_KEY, selectedAppCode);
  } else {
    window.localStorage.removeItem(APP_CODE_KEY);
  }
}

export function setRefreshHandler(handler: (() => Promise<boolean>) | null) {
  refreshHandler = handler;
}

export function setUnauthorizedHandler(handler: (() => void) | null) {
  unauthorizedHandler = handler;
}

export async function apiRequest<TData = unknown, TBody = unknown>(
  path: string,
  options: ApiRequestOptions<TBody> & { raw: true }
): Promise<Response>;
export async function apiRequest<TData = unknown, TBody = unknown>(
  path: string,
  options?: ApiRequestOptions<TBody> & { raw?: false }
): Promise<TData>;
export async function apiRequest<TData = unknown, TBody = unknown>(
  path: string,
  options?: ApiRequestOptions<TBody>
): Promise<Response | TData>;
export async function apiRequest<TData = unknown, TBody = unknown>(
  path: string,
  options: ApiRequestOptions<TBody> = {}
): Promise<Response | TData> {
  const {
    body,
    headers = {},
    method = body ? "POST" : "GET",
    query,
    retry = true,
    raw = false,
    skipAuth = false
  } = options;
  const url = buildURL(path, query);
  const requestHeaders = { ...headers };

  if (body !== undefined && !(body instanceof FormData)) {
    requestHeaders["Content-Type"] = "application/json";
  }
  if (accessToken && !skipAuth) {
    requestHeaders.Authorization = `Bearer ${accessToken}`;
  }
  if (selectedAppCode && !requestHeaders[APP_CODE_HEADER]) {
    requestHeaders[APP_CODE_HEADER] = selectedAppCode;
  }

  const response = await fetchWithNetworkRetry(url, {
    method,
    headers: requestHeaders,
    body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body),
    credentials: "include"
  });

  if ((response.status === 401 || response.status === 403) && retry && refreshHandler && !skipAuth) {
    const refreshed = await refreshOnce();
    if (refreshed) {
      return apiRequest(path, { ...options, retry: false });
    }
  }

  if (response.status === 401 && unauthorizedHandler && !skipAuth) {
    unauthorizedHandler();
  }

  if (raw) {
    if (!response.ok) {
      throw requestError(response.statusText || "请求失败", response.status);
    }
    return response;
  }

  const payload = await readJSON(response);
  if (!response.ok || payload.code !== 0) {
    throw requestError(resolveErrorMessage(response, payload.message), response.status);
  }

  return payload.data as TData;
}

function requestError(message: string, status: number) {
  const error = new Error(message) as Error & { status: number };
  // 保留 HTTP 状态供并发写入等业务分支精确判断，同时维持既有 Error message 行为。
  error.status = status;
  return error;
}

function refreshOnce() {
  if (!refreshHandler) {
    return Promise.resolve(false);
  }
  if (!refreshInFlight) {
    const handler = refreshHandler;
    refreshInFlight = handler().finally(() => {
      refreshInFlight = null;
    });
  }
  return refreshInFlight;
}

function resolveErrorMessage(response: Response, message?: string) {
  if (response.status === 404 && (!message || message.includes("404 page not found"))) {
    return "接口不存在或后端服务未更新";
  }
  return message || response.statusText || "请求失败";
}

async function fetchWithNetworkRetry(url: string, init: RequestInit) {
  const method = String(init.method || "GET").toUpperCase();
  const maxAttempts = method === "GET" || method === "HEAD" ? NETWORK_RETRY_ATTEMPTS + 1 : 1;
  let lastError: unknown;

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    try {
      return await fetch(url, init);
    } catch (error) {
      lastError = error;
      if (attempt >= maxAttempts) {
        break;
      }

      // GAAP occasionally resets the browser HTTP/2 stream before a response exists; retry only idempotent reads.
      await delay(NETWORK_RETRY_DELAY_MS * attempt);
    }
  }

  throw lastError;
}

function delay(ms: number) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, ms);
  });
}

function buildURL(path: string, query?: QueryParams) {
  const url = new URL(`${API_BASE_URL}${path}`, window.location.origin);
  if (query) {
    Object.entries(query).forEach(([key, value]) => {
      if (value !== undefined && value !== null && value !== "") {
        url.searchParams.set(key, String(value));
      }
    });
  }
  return url.toString();
}

async function readJSON<TData>(response: Response): Promise<ApiEnvelope<TData>> {
  const text = await response.text();
  if (!text) {
    return {};
  }
  try {
    return JSON.parse(text) as ApiEnvelope<TData>;
  } catch {
    return { code: response.ok ? 0 : response.status, message: text, data: text as TData };
  }
}

function normalizeAppCode(value: string) {
  return value.trim().toLowerCase();
}
