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

interface ILunatoneTestServer {
  url: string;
  controls: Array<{ path?: string; body: Record<string, unknown> }>;
  close(): Promise<void>;
}

const infoPayload = {
  name: 'DALI-2 Gateway',
  version: 'v1.17.0/1.4.3',
  tier: 'plus',
  uid: 'lunatone-uid-1',
  descriptor: { lines: 1 },
  device: {
    serial: 1234,
    gtin: 9010342013577,
    pcb: '9a',
    articleNumber: 89453886,
    articleInfo: '',
    productionYear: 2024,
    productionWeek: 1,
  },
  lines: {
    '0': {
      lineStatus: 'ok',
      device: {
        serial: 1234,
        gtin: 9010342013577,
        pcb: '9a',
        articleNumber: 89453886,
        articleInfo: '',
        productionYear: 2024,
        productionWeek: 1,
      },
    },
  },
};

const devicesPayload = {
  devices: [
    {
      id: 1,
      name: 'Office Light',
      type: 'default',
      available: true,
      address: 0,
      line: 0,
      features: {
        switchable: { status: true },
        dimmable: { status: 75 },
        colorRGB: { status: { r: 1, g: 0.5, b: 0 } },
      },
      groups: [],
      daliTypes: [],
    },
  ],
};

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));
  }
  const body = Buffer.concat(chunks).toString('utf8');
  return body ? JSON.parse(body) as Record<string, unknown> : {};
};

const startLunatoneServer = async (): Promise<ILunatoneTestServer> => {
  const controls: Array<{ path?: string; body: Record<string, unknown> }> = [];
  const server = http.createServer(async (requestArg, responseArg) => {
    const url = new URL(requestArg.url || '/', 'http://127.0.0.1');
    responseArg.setHeader('content-type', 'application/json');
    if (requestArg.method === 'GET' && url.pathname === '/info') {
      responseArg.end(JSON.stringify(infoPayload));
      return;
    }
    if (requestArg.method === 'GET' && url.pathname === '/devices') {
      responseArg.end(JSON.stringify(devicesPayload));
      return;
    }
    if (requestArg.method === 'POST' && url.pathname === '/device/1/control') {
      controls.push({ path: requestArg.url, body: await readJson(requestArg) });
      responseArg.end(JSON.stringify({ ok: true }));
      return;
    }
    responseArg.statusCode = 404;
    responseArg.end(JSON.stringify({ error: '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}`,
    controls,
    close: async () => close(server),
  };
};

tap.test('reads and controls Lunatone REST lights over native HTTP', async () => {
  const server = await startLunatoneServer();
  try {
    const client = new LunatoneClient({ url: server.url, timeoutMs: 1000, name: 'Gateway' });
    const snapshot = await client.getSnapshot(true);
    const runtime = await new LunatoneIntegration().setup({ url: server.url, timeoutMs: 1000, name: 'Gateway' }, {});
    const entities = await runtime.entities();
    const turnOff = await runtime.callService?.({ domain: 'light', service: 'turn_off', target: { entityId: 'light.gateway_device_1' } });

    expect(snapshot.online).toBeTrue();
    expect(snapshot.source).toEqual('http');
    expect(snapshot.entities.find((entityArg) => entityArg.id === 'device_1')?.state).toBeTrue();
    expect(entities.some((entityArg) => entityArg.id === 'light.gateway_device_1' && entityArg.state === true)).toBeTrue();
    expect(turnOff?.success).toBeTrue();
    expect(server.controls[0]?.path).toEqual('/device/1/control');
    expect(server.controls[0]?.body).toEqual({ dimmable: 0 });
    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();
