/**
 * In-memory mcpd mock for provider/engine tests. Implements the generic REST
 * shape the provider relies on: GET list, POST create, PUT /:id, DELETE /:id.
 * Records every request for assertions.
 */
import http from 'node:http';
import type { AddressInfo } from 'node:net';

export interface RecordedRequest {
  method: string;
  path: string;
  auth: string | undefined;
  body: unknown;
}

export interface MockMcpd {
  baseUrl: string;
  requests: RecordedRequest[];
  /** Seed/read the store directly: store[resource][id] = row. */
  store: Record<string, Map<string, Record<string, unknown>>>;
  seed(resource: string, row: Record<string, unknown>): void;
  /** Force the next N responses (any method) to a given status (e.g. 429). */
  failNextWith(status: number, times: number): void;
  close(): Promise<void>;
}

export async function startMockMcpd(): Promise<MockMcpd> {
  const store: Record<string, Map<string, Record<string, unknown>>> = {};
  const requests: RecordedRequest[] = [];
  let idCounter = 0;
  let forcedStatus: number | null = null;
  let forcedTimes = 0;

  const ensure = (resource: string) => {
    if (!store[resource]) store[resource] = new Map();
    return store[resource]!;
  };

  const server = http.createServer((req, res) => {
    const chunks: Buffer[] = [];
    req.on('data', (c: Buffer) => chunks.push(c));
    req.on('end', () => {
      const raw = Buffer.concat(chunks).toString('utf-8');
      const body = raw ? JSON.parse(raw) : undefined;
      const url = new URL(req.url ?? '/', 'http://mock');
      requests.push({ method: req.method ?? '', path: url.pathname, auth: req.headers['authorization'], body });

      if (forcedStatus !== null && forcedTimes > 0) {
        forcedTimes -= 1;
        res.statusCode = forcedStatus;
        res.end(JSON.stringify({ error: 'forced' }));
        return;
      }

      // /api/v1/<resource>[/<id>]
      const parts = url.pathname.split('/').filter(Boolean); // ["api","v1","<resource>", "<id>?"]
      const resource = parts[2] ?? '';
      const id = parts[3];
      const table = ensure(resource);

      const send = (status: number, payload?: unknown) => {
        res.statusCode = status;
        res.end(payload === undefined ? '' : JSON.stringify(payload));
      };

      if (req.method === 'GET' && !id) return send(200, [...table.values()]);
      if (req.method === 'POST' && !id) {
        idCounter += 1;
        const row = { id: `cmock${idCounter}0000000000000000000`, ...(body as Record<string, unknown>) };
        table.set(row.id, row);
        return send(201, row);
      }
      if (req.method === 'PUT' && id) {
        const existing = table.get(id);
        if (!existing) return send(404, { error: 'not found' });
        const updated = { ...existing, ...(body as Record<string, unknown>) };
        table.set(id, updated);
        return send(200, updated);
      }
      if (req.method === 'DELETE' && id) {
        if (!table.has(id)) return send(404, { error: 'not found' });
        table.delete(id);
        return send(200, { ok: true });
      }
      return send(404, { error: 'unhandled' });
    });
  });

  await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
  const { port } = server.address() as AddressInfo;

  return {
    baseUrl: `http://127.0.0.1:${port}`,
    requests,
    store,
    seed(resource, row) {
      ensure(resource).set(String(row['id']), row);
    },
    failNextWith(status, times) {
      forcedStatus = status;
      forcedTimes = times;
    },
    close() {
      return new Promise<void>((resolve) => server.close(() => resolve()));
    },
  };
}
