import { getAuthApiOrigin, getAuthBasePath } from "./env";
import { getTenantHeaderRecord } from "./tenant";

export type ApiCallResult<TBody = unknown> = {
  ok: boolean;
  status: number;
  statusText: string;
  athenaTime: string | null;
  proxyTime: string | null;
  body: TBody;
  message: string;
};

export type CallAuthEndpointOptions = {
  baseURL?: string;
  basePath?: string;
  tenant?: {
    tenantId?: string;
    headerName?: string;
  };
  requestDefaults?: Omit<RequestInit, "method" | "body">;
};

export async function callAuthEndpoint<TBody = unknown>(
  path: string,
  init?: RequestInit,
  options?: CallAuthEndpointOptions,
): Promise<ApiCallResult<TBody>> {
  const method = normalizeMethod(init?.method, init?.body);
  const url = resolveEndpointUrl(path, options?.baseURL, options?.basePath);
  const tenantHeaders = getTenantHeaderRecord(options?.tenant);
  const requestDefaults = options?.requestDefaults;
  const mergedInit = {
    ...requestDefaults,
    ...init,
  };
  const { body, requiresJsonContentType } = normalizeRequestBody(init?.body, method);
  const headers = mergeHeaders(
    tenantHeaders,
    requestDefaults?.headers,
    mergedInit.headers,
    requiresJsonContentType,
  );

  try {
    const credentials = mergedInit.credentials ?? "include";
    const response = await fetch(url, {
      ...requestDefaults,
      ...init,
      method,
      body,
      credentials,
      headers,
    });

    const rawText = await response.text();
    const parsedBody = parseResponseBody(rawText) as TBody;
    const message = extractResponseMessage(parsedBody, response);

    return {
      ok: response.ok,
      status: response.status,
      statusText: response.statusText,
      athenaTime:
        response.headers.get("x-athena-time") ??
        response.headers.get("x-athena-duration-ms"),
      proxyTime:
        response.headers.get("x-proxy-time") ??
        response.headers.get("x-proxy-duration-ms"),
      body: parsedBody,
      message,
    };
  } catch (error) {
    return {
      ok: false,
      status: 0,
      statusText: "Fetch failed",
      athenaTime: null,
      proxyTime: null,
      body: {} as TBody,
      message: error instanceof Error ? error.message : "Failed to fetch",
    };
  }
}

function resolveEndpointUrl(
  path: string,
  baseURL?: string,
  basePath?: string,
): string {
  if (/^https?:\/\//i.test(path)) {
    return path;
  }

  const origin = (baseURL ?? getAuthApiOrigin()).replace(/\/$/, "");
  const normalizedBasePath = getAuthBasePath({ authBasePath: basePath }).replace(/\/$/, "");
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;

  if (
    normalizedPath === normalizedBasePath ||
    normalizedPath.startsWith(`${normalizedBasePath}/`)
  ) {
    return `${origin}${normalizedPath}`;
  }

  return `${origin}${normalizedBasePath}${normalizedPath}`;
}

function normalizeMethod(
  method: string | undefined,
  body: RequestInit["body"],
): "GET" | "POST" {
  const normalized = method?.toUpperCase();
  if (normalized === "GET" || normalized === "POST") {
    return normalized;
  }

  return body == null ? "GET" : "POST";
}

function normalizeRequestBody(
  body: RequestInit["body"],
  method: "GET" | "POST",
): { body: BodyInit | undefined; requiresJsonContentType: boolean } {
  if (method === "GET" || body == null) {
    return { body: undefined, requiresJsonContentType: false };
  }

  if (
    typeof body === "string" ||
    body instanceof URLSearchParams ||
    body instanceof FormData ||
    body instanceof Blob ||
    body instanceof ArrayBuffer ||
    ArrayBuffer.isView(body)
  ) {
    return { body: body as BodyInit, requiresJsonContentType: typeof body === "string" };
  }

  return {
    body: JSON.stringify(body),
    requiresJsonContentType: true,
  };
}

function mergeHeaders(
  tenantHeaders: Record<string, string>,
  requestHeaders: HeadersInit | undefined,
  initHeaders: HeadersInit | undefined,
  requiresJsonContentType: boolean,
): Record<string, string> {
  const headers = new Headers(requestHeaders ?? {});
  const overrides = new Headers(initHeaders ?? {});
  overrides.forEach((value, key) => {
    headers.set(key, value);
  });
  for (const [key, value] of Object.entries(tenantHeaders)) {
    headers.set(key, value);
  }

  if (requiresJsonContentType && !headers.has("content-type")) {
    headers.set("content-type", "application/json");
  }

  const result: Record<string, string> = {};
  headers.forEach((value, key) => {
    result[key] = value;
  });
  return result;
}

function parseResponseBody(text: string): unknown {
  const trimmed = text.trim();
  if (!trimmed) return {};
  try {
    return JSON.parse(trimmed);
  } catch {
    return { raw: text };
  }
}

function extractResponseMessage(body: unknown, response: Response): string {
  if (body && typeof body === "object") {
    const record = body as Record<string, unknown>;
    if (typeof record.message === "string" && record.message) {
      return record.message;
    }
    if (typeof record.error === "string" && record.error) {
      return record.error;
    }
  }

  if (response.ok) return "OK";
  return response.statusText || `HTTP ${response.status}`;
}
