import type { ApiError } from './types';
import { ApiClientError } from './errors';

/**
 * API Client Configuration
 */
export interface ClientConfig {
  baseUrl: string;
  apiKey?: string;
  bearerToken?: string;
  headers?: Record<string, string>;
  /** Called on any error (after parsing) */
  onError?: (error: ApiClientError) => void;
  /** Called specifically on auth errors (401) - useful for triggering logout */
  onAuthError?: (error: ApiClientError) => void;
}

/**
 * Parse error response body into ApiError structure.
 */
async function parseErrorResponse(response: Response): Promise<ApiError> {
  try {
    const body = await response.json();
    // Handle wrapped response format { error: { ... } }
    if (body.error && typeof body.error === 'object') {
      return {
        status: response.status,
        code: body.error.code || 'UNKNOWN_ERROR',
        message: body.error.message || response.statusText,
        details: body.error.details,
      };
    }
    // Handle direct error format { code, message, details }
    if (body.code && body.message) {
      return {
        status: response.status,
        code: body.code,
        message: body.message,
        details: body.details,
      };
    }
    // Handle simple message format { message: "..." }
    if (body.message) {
      return {
        status: response.status,
        code: codeFromStatus(response.status),
        message: body.message,
        details: body.details,
      };
    }
    // Fallback
    return {
      status: response.status,
      code: codeFromStatus(response.status),
      message: response.statusText || 'An error occurred',
    };
  } catch {
    // Failed to parse JSON
    return {
      status: response.status,
      code: codeFromStatus(response.status),
      message: response.statusText || 'An error occurred',
    };
  }
}

/**
 * Map HTTP status to error code.
 */
function codeFromStatus(status: number): string {
  switch (status) {
    case 400:
      return 'BAD_REQUEST';
    case 401:
      return 'UNAUTHORIZED';
    case 403:
      return 'FORBIDDEN';
    case 404:
      return 'NOT_FOUND';
    case 409:
      return 'CONFLICT';
    case 422:
      return 'UNPROCESSABLE_ENTITY';
    case 429:
      return 'TOO_MANY_REQUESTS';
    case 503:
      return 'SERVICE_UNAVAILABLE';
    default:
      return status >= 500 ? 'INTERNAL_ERROR' : 'UNKNOWN_ERROR';
  }
}

/**
 * Create a typed API client
 *
 * @example
 * const client = createClient({
 *   baseUrl: 'https://api.example.com',
 *   apiKey: 'your-api-key',
 * });
 *
 * const users = await client.get('/users');
 * const newUser = await client.post('/users', { name: 'John' });
 *
 * @example
 * // Handling validation errors
 * try {
 *   await client.post('/users', { email: 'invalid' });
 * } catch (error) {
 *   if (isApiClientError(error) && error.isValidationError()) {
 *     const fieldErrors = error.getFieldErrors();
 *     console.log(fieldErrors.email); // "email must be a valid email address"
 *   }
 * }
 */
export function createClient(config: ClientConfig) {
  const { baseUrl, apiKey, bearerToken, headers = {}, onError, onAuthError } = config;

  async function request<T>(
    method: string,
    path: string,
    options: {
      body?: unknown;
      params?: Record<string, string | number | boolean | undefined>;
      headers?: Record<string, string>;
    } = {}
  ): Promise<T> {
    const url = new URL(path, baseUrl);

    // Add query params
    if (options.params) {
      for (const [key, value] of Object.entries(options.params)) {
        if (value !== undefined) {
          url.searchParams.set(key, String(value));
        }
      }
    }

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

    if (apiKey) {
      requestHeaders['X-API-Key'] = apiKey;
    }
    if (bearerToken) {
      requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
    }

    const response = await fetch(url.toString(), {
      method,
      headers: requestHeaders,
      body: options.body ? JSON.stringify(options.body) : undefined,
    });

    if (!response.ok) {
      const apiError = await parseErrorResponse(response);
      const error = new ApiClientError(apiError, response);

      // Call auth error handler for 401s
      if (error.isAuthError() && onAuthError) {
        onAuthError(error);
      }

      // Call general error handler
      if (onError) {
        onError(error);
      }

      throw error;
    }

    // Handle no-content responses
    if (response.status === 204) {
      return undefined as T;
    }

    const json = await response.json();

    // Handle wrapped response format { data: { ... } }
    if (json.data !== undefined) {
      return json.data as T;
    }

    return json as T;
  }

  return {
    get: <T>(path: string, params?: Record<string, string | number | boolean | undefined>) =>
      request<T>('GET', path, { params }),
    post: <T>(path: string, body?: unknown) => request<T>('POST', path, { body }),
    put: <T>(path: string, body?: unknown) => request<T>('PUT', path, { body }),
    patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, { body }),
    delete: <T>(path: string) => request<T>('DELETE', path),
  };
}
