import { useAuthStore } from '../store/auth'
import { useServerStore } from '../store/server'

const BASE = '/api'

// Singleton in-flight refresh promise. When multiple requests 401 at the same
// time (which is the common case — page load fires several queries in parallel)
// we only want to hit /api/auth/refresh once and let everyone await the result.
let refreshInFlight: Promise<boolean> | null = null

async function tryRefresh(): Promise<boolean> {
  if (refreshInFlight) return refreshInFlight

  refreshInFlight = (async () => {
    try {
      const res = await fetch(`${BASE}/auth/refresh`, {
        method: 'POST',
        credentials: 'same-origin',
      })
      if (!res.ok) {
        useAuthStore.getState().logout()
        return false
      }
      const data = (await res.json()) as { token: string }
      sessionStorage.setItem('token', data.token)
      // Refresh keeps the existing user info (no user payload in /refresh
      // response). We mutate the store's token field directly so components
      // re-render with the new auth state if they read it.
      useAuthStore.setState({ token: data.token })
      return true
    } catch {
      useAuthStore.getState().logout()
      return false
    } finally {
      refreshInFlight = null
    }
  })()

  return refreshInFlight
}

function buildRequest(path: string, options: RequestInit): Promise<Response> {
  const token = sessionStorage.getItem('token')
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(options.headers as Record<string, string>),
  }
  if (token) {
    headers['Authorization'] = `Bearer ${token}`
  }

  // credentials: 'same-origin' (the fetch default) ensures the __Secure-Fgp
  // fingerprint cookie is sent on every same-origin API call. The cookie is
  // HttpOnly so JavaScript cannot read or forge it; the browser attaches it
  // automatically, completing the OWASP token-sidejacking defense.
  return fetch(`${BASE}${path}`, {
    ...options,
    headers,
    credentials: 'same-origin',
  })
}

export async function apiFetch<T>(
  path: string,
  options: RequestInit = {},
): Promise<T> {
  // Don't recurse: /refresh itself never tries to refresh.
  const isRefreshCall = path === '/auth/refresh'

  let res = await buildRequest(path, options)

  if (res.status === 401 && !isRefreshCall) {
    const refreshed = await tryRefresh()
    if (refreshed) {
      res = await buildRequest(path, options)
    }
  }

  // Track which backend served this request
  const servedBy = res.headers.get('X-Served-By')
  if (servedBy) {
    useServerStore.getState().setServedBy(servedBy)
  }

  if (!res.ok) {
    const err = await res.json().catch(() => ({ error: res.statusText }))
    throw new Error(err.error ?? res.statusText)
  }

  // 204 No Content
  if (res.status === 204) return undefined as unknown as T

  return res.json() as Promise<T>
}
