export interface PigApiClientOptions {
  baseUrl: string;
  apiKey: string;
  fetchImpl?: typeof fetch;
  timeoutMs?: number;
}

export interface PigRequestOptions {
  method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
  query?: Record<string, string | number | boolean | undefined>;
  body?: unknown;
}

export class PigApiError extends Error {
  constructor(
    readonly code: string,
    message: string,
    readonly status?: number,
    readonly details?: unknown,
  ) {
    super(message);
    this.name = 'PigApiError';
  }
}

function record(value: unknown): Record<string, unknown> | null {
  return value !== null && typeof value === 'object' && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : null;
}

export class PigApiClient {
  private readonly baseUrl: string;
  private readonly fetchImpl: typeof fetch;
  private readonly timeoutMs: number;

  constructor(private readonly options: PigApiClientOptions) {
    this.baseUrl = options.baseUrl.replace(/\/+$/, '');
    this.fetchImpl = options.fetchImpl ?? fetch;
    this.timeoutMs = options.timeoutMs ?? 30_000;
  }

  async request<T>(path: string, options: PigRequestOptions = {}): Promise<T> {
    const url = new URL(`${this.baseUrl}${path}`);
    for (const [name, value] of Object.entries(options.query ?? {})) {
      if (value !== undefined) url.searchParams.set(name, String(value));
    }

    const hasBody = options.body !== undefined;
    let response: Response;
    try {
      response = await this.fetchImpl(url, {
        method: options.method ?? 'GET',
        headers: {
          accept: 'application/json',
          authorization: `Bearer ${this.options.apiKey}`,
          ...(hasBody ? { 'content-type': 'application/json' } : {}),
        },
        body: hasBody ? JSON.stringify(options.body) : undefined,
        signal: AbortSignal.timeout(this.timeoutMs),
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown network failure';
      throw new PigApiError('network_error', `Could not reach the PIG API: ${message}`);
    }

    const raw = await response.text();
    let payload: unknown = null;
    if (raw) {
      try {
        payload = JSON.parse(raw) as unknown;
      } catch {
        if (response.ok) {
          throw new PigApiError(
            'invalid_response',
            'The PIG API returned a non-JSON response.',
            response.status,
          );
        }
        payload = raw.slice(0, 1_000);
      }
    }

    if (!response.ok) {
      const body = record(payload);
      const nestedError = record(body?.error);
      const code =
        typeof body?.code === 'string'
          ? body.code
          : typeof nestedError?.code === 'string'
            ? nestedError.code
            : `http_${response.status}`;
      const message =
        typeof body?.error === 'string'
          ? body.error
          : typeof nestedError?.message === 'string'
            ? nestedError.message
          : `PIG API request failed with HTTP ${response.status}.`;
      throw new PigApiError(code, message, response.status, payload);
    }

    return payload as T;
  }
}
