/** Typed fetch wrapper with 401 interceptor and silent refresh. */

import { captureException } from '@/composables/usePostHog'

class ApiError extends Error {
  constructor(
    public status: number,
    public body: unknown,
    message?: string,
  ) {
    super(message ?? `API error ${status}`)
  }
}

export { ApiError }

// Module-scoped access token — populated after a successful refresh.
let accessToken: string | null = null

// Coalesce concurrent refresh attempts into a single request.
let refreshPromise: Promise<boolean> | null = null

async function silentRefresh(): Promise<boolean> {
  try {
    const res = await fetch('/auth/refresh', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({}),
    })
    if (!res.ok) return false
    const data = await res.json()
    if (data.access_token) {
      accessToken = data.access_token
      return true
    }
    return false
  } catch {
    return false
  }
}

function tryRefresh(): Promise<boolean> {
  if (!refreshPromise) {
    refreshPromise = silentRefresh().finally(() => {
      refreshPromise = null
    })
  }
  return refreshPromise
}

async function request<T>(url: string, init?: RequestInit): Promise<T> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(init?.headers as Record<string, string> | undefined),
  }
  if (accessToken) {
    headers['Authorization'] = `Bearer ${accessToken}`
  }

  const res = await fetch(url, { ...init, headers })

  if (res.status === 401) {
    const body = await res.json().catch(() => null)

    // GitHub OAuth 401s should not trigger refresh or redirect
    if (body?.error?.includes('GitHub')) {
      const err = new ApiError(401, body, body?.error ?? 'Unauthorized')
      captureException(err, { url, status: 401 })
      throw err
    }

    // Attempt silent refresh and retry
    const refreshed = await tryRefresh()
    if (refreshed && accessToken) {
      const retryHeaders: Record<string, string> = {
        'Content-Type': 'application/json',
        ...(init?.headers as Record<string, string> | undefined),
        Authorization: `Bearer ${accessToken}`,
      }
      const retryRes = await fetch(url, { ...init, headers: retryHeaders })

      if (retryRes.status === 401) {
        // Refresh succeeded but still 401 — redirect to login
        const retryBody401 = await retryRes.json().catch(() => null)
        const err = new ApiError(401, retryBody401, 'Unauthorized after refresh')
        captureException(err, { url, status: 401 })
        window.location.href = '/login'
        throw err
      }

      if (!retryRes.ok) {
        const retryErrBody = await retryRes.json().catch(() => null)
        const err = new ApiError(retryRes.status, retryErrBody, retryErrBody?.error ?? `API error ${retryRes.status}`)
        captureException(err, { url, status: retryRes.status })
        throw err
      }
      return (await retryRes.json()) as T
    }

    // Refresh failed — redirect to login
    window.location.href = '/login'
    throw new ApiError(401, body, body?.error ?? 'Unauthorized')
  }

  const body = await res.json()

  if (!res.ok) {
    const err = new ApiError(res.status, body, body?.error ?? `API error ${res.status}`)
    captureException(err, { url, status: res.status })
    throw err
  }

  return body as T
}

export function get<T>(url: string): Promise<T> {
  return request<T>(url)
}

export function post<T>(url: string, data?: unknown): Promise<T> {
  return request<T>(url, {
    method: 'POST',
    body: data ? JSON.stringify(data) : undefined,
  })
}
