/**
 * Базовый API-клиент VidConf.
 *
 * - Access-токен подставляется из authStore (память, не localStorage).
 * - На 401 выполняется один silent-refresh (POST /auth/refresh,
 *   credentials: 'include' — сессия читается из httpOnly-cookie) и повтор
 *   исходного запроса. Если refresh не удался — access-токен сбрасывается и
 *   выполняется редирект на /login.
 * - Параллельные 401 схлопываются в один refresh-запрос (refreshPromise).
 */
import { authStore } from '@/auth/authStore'

const API_BASE = '/api/v1'

/** Ошибка API с HTTP-статусом и разобранным телом ответа (если это JSON). */
export class ApiError extends Error {
  readonly status: number
  readonly detail: unknown

  constructor(status: number, detail: unknown, message?: string) {
    super(message ?? `Ошибка API: ${status}`)
    this.name = 'ApiError'
    this.status = status
    this.detail = detail
  }
}

/**
 * Извлекает машиночитаемый код ошибки из тела ответа FastAPI
 * (`{"detail": "some_code"}`) — используется для различения причин 403/409
 * (напр. "password_required" vs "invalid_password").
 */
export function errorDetail(err: unknown): string | undefined {
  if (err instanceof ApiError && err.detail && typeof err.detail === 'object' && 'detail' in err.detail) {
    const value = (err.detail as { detail: unknown }).detail
    return typeof value === 'string' ? value : undefined
  }
  return undefined
}

interface RequestOptions extends Omit<RequestInit, 'body'> {
  body?: unknown
  /** Не пытаться делать silent-refresh при 401 (используется самим refresh-запросом). */
  skipAuthRefresh?: boolean
}

let refreshPromise: Promise<boolean> | null = null

/**
 * Выполняет silent-refresh access-токена через httpOnly refresh-cookie.
 * Возвращает true при успехе. Параллельные вызовы переиспользуют один запрос.
 */
export async function refreshAccessToken(): Promise<boolean> {
  if (!refreshPromise) {
    refreshPromise = (async () => {
      try {
        const response = await fetch(`${API_BASE}/auth/refresh`, {
          method: 'POST',
          credentials: 'include',
        })
        if (!response.ok) return false
        const data = (await response.json()) as { access_token: string }
        authStore.setAccessToken(data.access_token)
        return true
      } catch {
        return false
      } finally {
        refreshPromise = null
      }
    })()
  }
  return refreshPromise
}

/** Редирект на страницу входа при невосстановимой потере сессии. */
function redirectToLogin(): void {
  authStore.setAccessToken(null)
  if (window.location.pathname !== '/login') {
    window.location.assign('/login')
  }
}

async function parseBody(response: Response): Promise<unknown> {
  const text = await response.text()
  if (!text) return null
  try {
    return JSON.parse(text)
  } catch {
    return text
  }
}

/**
 * Базовый fetch-запрос к API с автоподстановкой access-токена и
 * silent-refresh на 401.
 */
export async function apiRequest<T = unknown>(path: string, options: RequestOptions = {}): Promise<T> {
  const { body, skipAuthRefresh, headers, ...rest } = options

  const doFetch = async (): Promise<Response> => {
    const finalHeaders = new Headers(headers)
    const token = authStore.getAccessToken()
    if (token) finalHeaders.set('Authorization', `Bearer ${token}`)

    let finalBody: BodyInit | undefined
    if (body !== undefined && !(body instanceof URLSearchParams) && !(body instanceof FormData)) {
      finalHeaders.set('Content-Type', 'application/json')
      finalBody = JSON.stringify(body)
    } else if (body instanceof URLSearchParams) {
      finalHeaders.set('Content-Type', 'application/x-www-form-urlencoded')
      finalBody = body
    } else if (body instanceof FormData) {
      finalBody = body
    }

    return fetch(`${API_BASE}${path}`, {
      ...rest,
      headers: finalHeaders,
      body: finalBody,
      credentials: 'include',
    })
  }

  let response = await doFetch()

  if (response.status === 401 && !skipAuthRefresh) {
    const refreshed = await refreshAccessToken()
    if (refreshed) {
      response = await doFetch()
    } else {
      redirectToLogin()
      throw new ApiError(401, null, 'Сессия истекла')
    }
  }

  if (!response.ok) {
    const detail = await parseBody(response)
    throw new ApiError(response.status, detail)
  }

  if (response.status === 204) return undefined as T
  return (await parseBody(response)) as T
}
