import http from 'node:http';

export interface ApiClientOptions {
  baseUrl: string;
  timeout?: number | undefined;
  token?: string | undefined;
}

export interface ApiResponse<T = unknown> {
  status: number;
  data: T;
}

export class ApiError extends Error {
  constructor(
    public readonly status: number,
    public readonly body: string,
  ) {
    super(`API error ${status}: ${body}`);
    this.name = 'ApiError';
  }
}

function request<T>(method: string, url: string, timeout: number, body?: unknown, token?: string): Promise<ApiResponse<T>> {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const headers: Record<string, string> = {};
    if (body !== undefined) {
      headers['Content-Type'] = 'application/json';
    }
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
    const opts: http.RequestOptions = {
      hostname: parsed.hostname,
      port: parsed.port,
      path: parsed.pathname + parsed.search,
      method,
      timeout,
      headers,
    };

    const req = http.request(opts, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (chunk: Buffer) => chunks.push(chunk));
      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString('utf-8');
        const status = res.statusCode ?? 0;
        if (status >= 400) {
          reject(new ApiError(status, raw));
          return;
        }
        try {
          resolve({ status, data: JSON.parse(raw) as T });
        } catch {
          resolve({ status, data: raw as unknown as T });
        }
      });
    });
    req.on('error', reject);
    req.on('timeout', () => {
      req.destroy();
      reject(new Error(`Request to ${url} timed out`));
    });
    if (body !== undefined) {
      req.write(JSON.stringify(body));
    }
    req.end();
  });
}

export class ApiClient {
  private baseUrl: string;
  private timeout: number;
  private token?: string | undefined;

  constructor(opts: ApiClientOptions) {
    this.baseUrl = opts.baseUrl.replace(/\/$/, '');
    this.timeout = opts.timeout ?? 10000;
    this.token = opts.token;
  }

  async get<T = unknown>(path: string): Promise<T> {
    const res = await request<T>('GET', `${this.baseUrl}${path}`, this.timeout, undefined, this.token);
    return res.data;
  }

  async post<T = unknown>(path: string, body?: unknown): Promise<T> {
    const res = await request<T>('POST', `${this.baseUrl}${path}`, this.timeout, body, this.token);
    return res.data;
  }

  async put<T = unknown>(path: string, body?: unknown): Promise<T> {
    const res = await request<T>('PUT', `${this.baseUrl}${path}`, this.timeout, body, this.token);
    return res.data;
  }

  async delete(path: string): Promise<void> {
    await request('DELETE', `${this.baseUrl}${path}`, this.timeout, undefined, this.token);
  }
}
