import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import http from 'node:http';
import { ApiClient, ApiError } from '../src/api-client.js';

let server: http.Server;
let port: number;

beforeAll(async () => {
  server = http.createServer((req, res) => {
    if (req.url === '/api/v1/servers' && req.method === 'GET') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify([{ id: 'srv-1', name: 'slack' }]));
    } else if (req.url === '/api/v1/servers/srv-1' && req.method === 'GET') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ id: 'srv-1', name: 'slack', transport: 'STDIO' }));
    } else if (req.url === '/api/v1/servers' && req.method === 'POST') {
      const chunks: Buffer[] = [];
      req.on('data', (c: Buffer) => chunks.push(c));
      req.on('end', () => {
        const body = JSON.parse(Buffer.concat(chunks).toString());
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ id: 'srv-new', ...body }));
      });
    } else if (req.url === '/api/v1/servers/srv-1' && req.method === 'DELETE') {
      // Fastify rejects empty body with Content-Type: application/json
      const ct = req.headers['content-type'] ?? '';
      if (ct.includes('application/json')) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: "Body cannot be empty when content-type is set to 'application/json'" }));
      } else {
        res.writeHead(204);
        res.end();
      }
    } else if (req.url === '/api/v1/missing' && req.method === 'GET') {
      res.writeHead(404, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Not found' }));
    } else {
      res.writeHead(404);
      res.end();
    }
  });

  await new Promise<void>((resolve) => {
    server.listen(0, () => {
      const addr = server.address();
      if (addr && typeof addr === 'object') {
        port = addr.port;
      }
      resolve();
    });
  });
});

afterAll(() => {
  server.close();
});

describe('ApiClient', () => {
  it('performs GET request for list', async () => {
    const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
    const result = await client.get<Array<{ id: string; name: string }>>('/api/v1/servers');
    expect(result).toEqual([{ id: 'srv-1', name: 'slack' }]);
  });

  it('performs GET request for single item', async () => {
    const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
    const result = await client.get<{ id: string; name: string }>('/api/v1/servers/srv-1');
    expect(result.name).toBe('slack');
  });

  it('performs POST request', async () => {
    const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
    const result = await client.post<{ id: string; name: string }>('/api/v1/servers', { name: 'github' });
    expect(result.id).toBe('srv-new');
    expect(result.name).toBe('github');
  });

  it('throws ApiError on 404', async () => {
    const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
    await expect(client.get('/api/v1/missing')).rejects.toThrow(ApiError);
  });

  it('throws on connection error', async () => {
    const client = new ApiClient({ baseUrl: 'http://localhost:1' });
    await expect(client.get('/anything')).rejects.toThrow();
  });

  it('performs DELETE without Content-Type header', async () => {
    const client = new ApiClient({ baseUrl: `http://localhost:${port}` });
    // Should succeed (204) because no Content-Type is sent on bodyless DELETE
    await expect(client.delete('/api/v1/servers/srv-1')).resolves.toBeUndefined();
  });

  it('sends Authorization header when token provided', async () => {
    // We need a separate server to check the header
    let receivedAuth = '';
    const authServer = http.createServer((req, res) => {
      receivedAuth = req.headers['authorization'] ?? '';
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ ok: true }));
    });
    const authPort = await new Promise<number>((resolve) => {
      authServer.listen(0, () => {
        const addr = authServer.address();
        if (addr && typeof addr === 'object') resolve(addr.port);
      });
    });
    try {
      const client = new ApiClient({ baseUrl: `http://localhost:${authPort}`, token: 'my-token' });
      await client.get('/test');
      expect(receivedAuth).toBe('Bearer my-token');
    } finally {
      authServer.close();
    }
  });
});
