import { configuredBackendUrl } from "@/lib/backend-url"
import {
  getToken,
  isAuthEnabled,
  redirectToPortalLogin,
  redirectToPortalLoginInteractive,
} from "@/lib/auth"

export class ApiClientError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly payload?: unknown,
  ) {
    super(message)
    this.name = "ApiClientError"
  }
}

function trimBaseUrl(baseUrl: string): string {
  return baseUrl.replace(/\/$/, "")
}

/** Absolute or same-origin-relative URL for backend API paths. */
export function resolveApiUrl(baseUrl: string, path: string): string {
  if (path.startsWith("/") && configuredBackendUrl().kind === "same-origin") {
    return path
  }
  // Safety: never call browser localhost when the UI is served from a remote host
  if (typeof window !== "undefined") {
    const host = window.location.hostname
    const remoteUi = host !== "localhost" && host !== "127.0.0.1"
    const baseIsLocal =
      /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(trimBaseUrl(baseUrl))
    if (remoteUi && (baseIsLocal || !baseUrl.trim())) {
      return path.startsWith("/") ? path : `/${path}`
    }
  }
  return trimBaseUrl(baseUrl) + path
}

/** Attach portal JWT when present. */
export function withAuthHeaders(init?: HeadersInit): Headers {
  const headers = new Headers(init)
  const token = typeof window !== "undefined" ? getToken() : null
  if (token && !headers.has("Authorization")) {
    headers.set("Authorization", `Bearer ${token}`)
  }
  return headers
}

function handleUnauthorized(): never {
  if (typeof window !== "undefined" && isAuthEnabled()) {
    const ok = redirectToPortalLogin()
    if (!ok) redirectToPortalLoginInteractive()
  }
  throw new ApiClientError("Unauthorized", 401)
}

async function parseErrorMessage(res: Response): Promise<string> {
  const payload = await res.json().catch(() => undefined)
  if (
    typeof payload === "object" &&
    payload !== null &&
    "error" in payload &&
    typeof (payload as { error?: unknown }).error === "string"
  ) {
    return (payload as { error: string }).error
  }
  return res.statusText || `HTTP ${res.status}`
}

export async function requestJson<T>(
  baseUrl: string,
  path: string,
  init?: RequestInit,
): Promise<T> {
  const hasBody = init?.body != null
  const headers = withAuthHeaders(init?.headers)
  if (hasBody && !headers.has("Content-Type")) {
    headers.set("Content-Type", "application/json")
  }

  const res = await fetch(resolveApiUrl(baseUrl, path), {
    ...init,
    headers,
  })

  if (res.status === 401) handleUnauthorized()

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

  const payload = await res.json().catch(() => undefined)
  if (!res.ok) {
    const msg =
      typeof payload === "object" &&
      payload !== null &&
      "error" in payload &&
      typeof (payload as { error?: unknown }).error === "string"
        ? (payload as { error: string }).error
        : res.statusText
    throw new ApiClientError(msg, res.status, payload)
  }

  return payload as T
}

/** Binary/download endpoints (backup, backup file) with the same auth + URL rules. */
export async function requestBlob(
  baseUrl: string,
  path: string,
  init?: RequestInit,
): Promise<Response> {
  const headers = withAuthHeaders(init?.headers)
  const res = await fetch(resolveApiUrl(baseUrl, path), {
    ...init,
    headers,
  })
  if (res.status === 401) handleUnauthorized()
  if (!res.ok) {
    throw new ApiClientError(await parseErrorMessage(res), res.status)
  }
  return res
}
