import { createServer } from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { HyperionClient, HyperionIntegration, HyperionMapper, type IHyperionCommandRequest, type IHyperionJsonRpcRequest, type IHyperionRawData } from '../../ts/integrations/hyperion/index.js';

const rawData: IHyperionRawData = {
  sysInfo: { hyperion: { id: 'static-hyperion', version: '2.0.17' } },
  serverInfo: {
    instance: [{ instance: 0, friendly_name: 'Static Hyperion', running: true }],
    components: [{ name: 'ALL', enabled: true }, { name: 'LEDDEVICE', enabled: true }],
    effects: [{ name: 'Rainbow swirl' }],
    adjustment: [{ id: 'default', brightness: 75 }],
    priorities: [{ priority: 128, componentId: 'COLOR', origin: 'smarthome.exchange', visible: true, value: { RGB: [5, 6, 7] } }],
  },
};

const responseFor = (payloadArg: IHyperionJsonRpcRequest): Record<string, unknown> => {
  if (payloadArg.command === 'authorize' && payloadArg.subcommand === 'tokenRequired') {
    return { command: 'authorize-tokenRequired', info: { required: false }, success: true, tan: payloadArg.tan };
  }
  if (payloadArg.command === 'sysinfo') {
    return { command: 'sysinfo', info: { hyperion: { id: 'http-hyperion', version: '2.0.17' }, system: { hostName: 'hyperion-host' } }, success: true, tan: payloadArg.tan };
  }
  if (payloadArg.command === 'serverinfo') {
    return { command: 'serverinfo-getInfo', info: rawData.serverInfo, success: true, tan: payloadArg.tan };
  }
  if (payloadArg.command === 'instance-data') {
    return { command: 'instance-data-getImageSnapshot', info: { data: 'aW1hZ2U=', format: payloadArg.format || 'PNG', width: 1, height: 1 }, success: true, tan: payloadArg.tan };
  }
  return { command: payloadArg.command, success: true, tan: payloadArg.tan };
};

tap.test('reads Hyperion snapshots over local HTTP JSON-RPC and sends commands', async () => {
  const requests: IHyperionJsonRpcRequest[] = [];
  const server = createServer((requestArg, responseArg) => {
    let body = '';
    requestArg.on('data', (chunkArg) => { body += chunkArg.toString('utf8'); });
    requestArg.on('end', () => {
      const payload = JSON.parse(body) as IHyperionJsonRpcRequest;
      requests.push(payload);
      responseArg.setHeader('content-type', 'application/json');
      responseArg.end(JSON.stringify(responseFor(payload)));
    });
  });

  await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const port = typeof address === 'object' && address ? address.port : 0;
    const client = new HyperionClient({ host: '127.0.0.1', port, transport: 'http', timeoutMs: 1000 });
    const snapshot = await client.getSnapshot();
    const color = await client.execute({ action: 'set_color', instance: 0, color: [1, 2, 3], priority: 128, origin: 'smarthome.exchange' });
    const image = await client.execute({ action: 'get_image_snapshot', instance: 0, format: 'PNG' });

    expect(snapshot.online).toBeTrue();
    expect(snapshot.source).toEqual('http');
    expect(snapshot.device.id).toEqual('http-hyperion');
    expect(snapshot.instances[0].name).toEqual('Static Hyperion');
    expect((color as { command?: string }).command).toEqual('color');
    expect((image as { format?: string }).format).toEqual('PNG');
    expect(requests.some((requestArg) => requestArg.command === 'authorize' && requestArg.subcommand === 'tokenRequired')).toBeTrue();
    expect(requests.some((requestArg) => requestArg.command === 'color' && (requestArg.color as number[])[0] === 1)).toBeTrue();
  } finally {
    await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
  }
});

tap.test('does not report live command success for static snapshots without transport', async () => {
  const snapshot = HyperionMapper.toSnapshot({ config: { name: 'Static Hyperion' }, rawData, online: true, source: 'manual' });
  const runtime = await new HyperionIntegration().setup({ snapshot }, {});
  const result = await runtime.callService!({ domain: 'light', service: 'turn_off', target: { entityId: 'light.static_hyperion_light' } });

  expect(result.success).toBeFalse();
  expect(result.error).toContain('Static snapshots/manual data are read-only');
  await runtime.destroy();
});

tap.test('delegates Hyperion commands to an injected executor', async () => {
  const commands: IHyperionCommandRequest[] = [];
  const snapshot = HyperionMapper.toSnapshot({ config: { name: 'Executor Hyperion' }, rawData, online: true, source: 'manual' });
  const runtime = await new HyperionIntegration().setup({
    snapshot,
    commandExecutor: {
      execute: async (requestArg) => {
        commands.push(requestArg);
        return { accepted: true };
      },
    },
  }, {});

  const result = await runtime.callService!({ domain: 'light', service: 'turn_on', target: { entityId: 'light.executor_hyperion_light' }, data: { rgb_color: [9, 8, 7] } });

  expect(result.success).toBeTrue();
  expect(commands[0].action).toEqual('set_color');
  expect(commands[0].color).toEqual([9, 8, 7]);
  await runtime.destroy();
});

export default tap.start();
