import { safeGetItem, safeRemoveItem, safeSetItem } from "../lib/safeStorage";

export type ApiEnvelope<T> = {
  code: number;
  message: string;
  data?: T;
  error?: {
    biz_code?: string;
    scope?: number;
    category?: number;
    detail?: number;
  };
};

export class ApiError extends Error {
  status: number;
  code?: number;
  bizCode?: string;

  constructor(message: string, status: number, code?: number, bizCode?: string) {
    super(message);
    this.name = "ApiError";
    this.status = status;
    this.code = code;
    this.bizCode = bizCode;
  }
}

const accessTokenKey = "haixun.access_token";
const refreshTokenKey = "haixun.refresh_token";

let onUnauthorized: (() => void) | undefined;
let refreshPromise: Promise<boolean> | undefined;

export function setUnauthorizedHandler(handler: (() => void) | undefined) {
  onUnauthorized = handler;
}

export function getAccessToken() {
  return safeGetItem(accessTokenKey);
}

export function getRefreshToken() {
  return safeGetItem(refreshTokenKey);
}

export function setTokens(accessToken: string, refreshToken: string) {
  safeSetItem(accessTokenKey, accessToken);
  safeSetItem(refreshTokenKey, refreshToken);
}

export function clearTokens() {
  safeRemoveItem(accessTokenKey);
  safeRemoveItem(refreshTokenKey);
}

type RequestOptions = {
  method?: string;
  body?: unknown;
  auth?: boolean;
  memberAuth?: boolean;
  providerToken?: string;
  headers?: Record<string, string>;
  retryOnUnauthorized?: boolean;
};

function isTransientGatewayError(status: number) {
  return status === 502 || status === 503 || status === 504;
}

function gatewayRetryDelayMs(attempt: number) {
  return 400 + attempt * 600;
}

async function sleep(ms: number) {
  await new Promise((resolve) => window.setTimeout(resolve, ms));
}

async function readApiEnvelope<T>(response: Response): Promise<ApiEnvelope<T> | null> {
  return (await response.json().catch(() => null)) as ApiEnvelope<T> | null;
}

function isProxyUnavailable(response: Response, envelope: ApiEnvelope<unknown> | null) {
  if (!isTransientGatewayError(response.status)) {
    return false;
  }
  const contentType = response.headers.get("content-type") || "";
  if (!contentType.includes("application/json")) {
    return true;
  }
  return envelope == null;
}

export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
  const maxAttempts = 4;
  let response: Response | null = null;
  let envelope: ApiEnvelope<T> | null = null;

  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    response = await fetch(path, buildRequestInit(options));
    envelope = await readApiEnvelope<T>(response);
    if (!isProxyUnavailable(response, envelope) || attempt === maxAttempts - 1) {
      break;
    }
    await sleep(gatewayRetryDelayMs(attempt));
  }

  if (!response) {
    throw new ApiError("後端服務暫時無法連線，請稍後再試", 502);
  }

  if (response.status === 401 && options.auth && options.retryOnUnauthorized !== false) {
    const refreshed = await refreshAccessToken();
    if (refreshed) {
      return apiRequest<T>(path, { ...options, retryOnUnauthorized: false });
    }
    onUnauthorized?.();
  }

  if (!response.ok || !envelope || envelope.code !== 102000) {
    const message = envelope?.message?.trim();
    const fallback = isProxyUnavailable(response, envelope)
      ? "後端服務暫時無法連線，請確認 API gateway 與反向代理狀態。"
      : `API request failed (${response.status})`;
    const detail =
      message ||
      (response.status === 502
        ? isProxyUnavailable(response, envelope)
          ? "請求逾時或閘道無回應（HTTP 502）。請稍後重試；若持續失敗請確認 API gateway 與 worker 狀態。"
          : "Threads 或外部 API 回傳失敗（HTTP 502）。請到帳號頁確認 Threads API 連線與 threads_keyword_search 權限。"
        : fallback);
    throw new ApiError(detail, response.status, envelope?.code, envelope?.error?.biz_code);
  }
  return envelope.data as T;
}

function buildRequestInit(options: RequestOptions): RequestInit {
  const headers: Record<string, string> = {
    Accept: "application/json",
    ...options.headers
  };
  if (options.body !== undefined) {
    headers["Content-Type"] = "application/json";
  }
  if (options.auth) {
    const token = getAccessToken();
    if (token) {
      headers.Authorization = `Bearer ${token}`;
    }
  }
  if (options.memberAuth) {
    const token = getAccessToken();
    if (token) {
      headers["X-Member-Authorization"] = `Bearer ${token}`;
    }
  }
  if (options.providerToken) {
    headers.Authorization = `Bearer ${options.providerToken}`;
  }
  return {
    method: options.method || (options.body === undefined ? "GET" : "POST"),
    headers,
    body: options.body === undefined ? undefined : JSON.stringify(options.body)
  };
}

async function refreshAccessToken(): Promise<boolean> {
  if (refreshPromise) {
    return refreshPromise;
  }

  refreshPromise = doRefreshAccessToken().finally(() => {
    refreshPromise = undefined;
  });
  return refreshPromise;
}

async function doRefreshAccessToken(): Promise<boolean> {
  const refreshToken = getRefreshToken();
  if (!refreshToken) {
    clearTokens();
    return false;
  }
  try {
    const data = await apiRequest<{ access_token: string; refresh_token: string }>("/api/v1/auth/refresh", {
      method: "POST",
      body: { refresh_token: refreshToken },
      retryOnUnauthorized: false
    });
    setTokens(data.access_token, data.refresh_token);
    return true;
  } catch {
    clearTokens();
    return false;
  }
}
