/**
 * HTTP API client with authentication and error handling.
 *
 * Provides typed fetch wrapper that automatically:
 * - Injects Authorization header from auth store
 * - Refreshes tokens on 401 responses
 * - Parses JSON responses
 * - Throws typed ApiError on failures
 */
import { config } from '@/config'
import { useAuthStore } from '@/stores/auth'
import { ApiError } from './types'
import type { RequestOptions, ErrorResponse } from './types'

/**
 * Build URL with query parameters.
 */
function buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
  const base = config.apiBaseUrl.replace(/\/$/, '')
  const cleanPath = path.startsWith('/') ? path : `/${path}`
  const url = new URL(`${base}${cleanPath}`)

  if (params) {
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined) {
        url.searchParams.set(key, String(value))
      }
    })
  }

  return url.toString()
}

/**
 * Parse error response from backend.
 */
async function parseErrorResponse(response: Response): Promise<ApiError> {
  let detail: string | undefined
  let code: string | undefined

  try {
    const data: ErrorResponse = await response.json()
    detail = data.detail || data.message
    code = data.code
  } catch {
    // Response body is not JSON or empty
  }

  return new ApiError(response.status, response.statusText, detail, code)
}

/**
 * Make an authenticated API request.
 *
 * @param method - HTTP method
 * @param path - API path (e.g., '/api/users/me')
 * @param options - Request options
 * @returns Parsed JSON response
 * @throws ApiError on non-2xx responses
 */
async function request<T>(
  method: string,
  path: string,
  options: RequestOptions = {}
): Promise<T> {
  const { skipAuth = false, headers = {}, body, params, signal } = options
  const auth = useAuthStore()

  // Build headers
  const requestHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    ...headers,
  }

  // Add auth header if authenticated and not skipped
  if (!skipAuth && auth.isAuthenticated) {
    const token = await auth.getValidToken()
    if (token) {
      requestHeaders['Authorization'] = `Bearer ${token}`
    }
  }

  // Make request
  const url = buildUrl(path, params)
  const response = await fetch(url, {
    method,
    headers: requestHeaders,
    body: body !== undefined ? JSON.stringify(body) : undefined,
    signal,
  })

  // Handle 401 - try to refresh and retry once
  if (response.status === 401 && !skipAuth && auth.refreshToken) {
    const refreshed = await auth.refreshAccessToken()
    if (refreshed) {
      // Retry with new token
      const newToken = await auth.getValidToken()
      if (newToken) {
        requestHeaders['Authorization'] = `Bearer ${newToken}`
        const retryResponse = await fetch(url, {
          method,
          headers: requestHeaders,
          body: body !== undefined ? JSON.stringify(body) : undefined,
          signal,
        })

        if (!retryResponse.ok) {
          throw await parseErrorResponse(retryResponse)
        }

        // Handle 204 No Content
        if (retryResponse.status === 204) {
          return undefined as T
        }

        return retryResponse.json()
      }
    }

    // Refresh failed, throw the original 401
    throw await parseErrorResponse(response)
  }

  // Handle other errors
  if (!response.ok) {
    throw await parseErrorResponse(response)
  }

  // Handle 204 No Content
  if (response.status === 204) {
    return undefined as T
  }

  return response.json()
}

/**
 * API client with typed methods for each HTTP verb.
 */
export const apiClient = {
  /**
   * Make a GET request.
   */
  get<T>(path: string, options?: RequestOptions): Promise<T> {
    return request<T>('GET', path, options)
  },

  /**
   * Make a POST request.
   */
  post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
    return request<T>('POST', path, { ...options, body })
  },

  /**
   * Make a PUT request.
   */
  put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
    return request<T>('PUT', path, { ...options, body })
  },

  /**
   * Make a PATCH request.
   */
  patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
    return request<T>('PATCH', path, { ...options, body })
  },

  /**
   * Make a DELETE request.
   */
  delete<T>(path: string, options?: RequestOptions): Promise<T> {
    return request<T>('DELETE', path, options)
  },
}

export default apiClient
