import * as http from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { LuciClient, LuciIntegration } from '../../ts/integrations/luci/index.js';

interface ILuciTestServer {
  url: string;
  calls: Array<{ path?: string; method?: string }>;
  close(): Promise<void>;
}

const readJson = async (requestArg: http.IncomingMessage): Promise<Record<string, unknown>> => {
  const chunks: Buffer[] = [];
  for await (const chunk of requestArg) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  }
  return JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}') as Record<string, unknown>;
};

const sendRpc = (responseArg: http.ServerResponse, resultArg: unknown, errorArg: unknown = null): void => {
  responseArg.statusCode = 200;
  responseArg.setHeader('content-type', 'application/json');
  responseArg.end(JSON.stringify({ result: resultArg, error: errorArg }));
};

const startLuciServer = async (): Promise<ILuciTestServer> => {
  const calls: Array<{ path?: string; method?: string }> = [];
  const server = http.createServer(async (requestArg, responseArg) => {
    const url = new URL(requestArg.url || '/', 'http://127.0.0.1');
    const body = await readJson(requestArg);
    const method = String(body.method || '');
    calls.push({ path: url.pathname, method });

    if (url.pathname === '/cgi-bin/luci/rpc/auth' && method === 'login') {
      sendRpc(responseArg, 'test-token');
      return;
    }
    if (url.searchParams.get('auth') !== 'test-token') {
      responseArg.statusCode = 403;
      responseArg.end('invalid token');
      return;
    }
    if (url.pathname === '/cgi-bin/luci/rpc/sys' && method === 'exec') {
      sendRpc(responseArg, '19.07.0\n');
      return;
    }
    if (url.pathname === '/cgi-bin/luci/rpc/ip' && method === 'neighbors') {
      sendRpc(responseArg, [
        { mac: 'AA:BB:CC:DD:EE:FF', dest: '192.168.1.20', reachable: true, dev: 'br-lan' },
        { mac: '11:22:33:44:55:66', dest: '192.168.1.30', reachable: false, dev: 'br-lan' },
      ]);
      return;
    }
    if (url.pathname === '/cgi-bin/luci/rpc/uci' && method === 'get_all') {
      sendRpc(responseArg, {
        cfg1: { '.type': 'host', mac: 'AA:BB:CC:DD:EE:FF', name: 'phone' },
      });
      return;
    }
    responseArg.statusCode = 404;
    responseArg.end('not found');
  });
  await listen(server);
  const address = server.address();
  const port = typeof address === 'object' && address ? address.port : 0;
  return {
    url: `http://127.0.0.1:${port}`,
    calls,
    close: async () => close(server),
  };
};

tap.test('reads OpenWrt LuCI JSON-RPC devices over native HTTP', async () => {
  const server = await startLuciServer();
  try {
    const client = new LuciClient({ url: server.url, username: 'root', password: 'secret', timeoutMs: 1000, name: 'OpenWrt Router' });
    const snapshot = await client.getSnapshot(true);
    const runtime = await new LuciIntegration().setup({ url: server.url, username: 'root', password: 'secret', timeoutMs: 1000, name: 'OpenWrt Router' }, {});
    const entities = await runtime.entities();

    expect(snapshot.online).toBeTrue();
    expect(snapshot.source).toEqual('http');
    expect(snapshot.entities.find((entityArg) => entityArg.id === 'connected_devices')?.state).toEqual(1);
    expect(snapshot.entities.some((entityArg) => entityArg.name === 'phone Connected')).toBeTrue();
    expect(entities.some((entityArg) => entityArg.name === 'phone Connected' && entityArg.available)).toBeTrue();
    expect(server.calls.some((callArg) => callArg.path === '/cgi-bin/luci/rpc/auth' && callArg.method === 'login')).toBeTrue();
    expect(server.calls.some((callArg) => callArg.path === '/cgi-bin/luci/rpc/ip' && callArg.method === 'neighbors')).toBeTrue();
    await runtime.destroy();
  } finally {
    await server.close();
  }
});

const listen = async (serverArg: http.Server): Promise<void> => {
  await new Promise<void>((resolve, reject) => {
    serverArg.once('error', reject);
    serverArg.listen(0, '127.0.0.1', () => {
      serverArg.off('error', reject);
      resolve();
    });
  });
};

const close = async (serverArg: http.Server): Promise<void> => {
  await new Promise<void>((resolve, reject) => {
    serverArg.close((errorArg) => errorArg ? reject(errorArg) : resolve());
  });
};

export default tap.start();
