import { createServer } from 'node:net';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { HomeAssistantNoboHubIntegration, NoboHubClient, NoboHubConfigFlow, NoboHubIntegration, NoboHubMapper, createNoboHubDiscoveryDescriptor, noboHubProfile, type INoboHubSnapshot, type TNoboHubRawData } from '../../ts/integrations/nobo_hub/index.js';

const rawData: TNoboHubRawData = {
  device: {
    id: 'nobo_hub-device-1',
    name: "Nob Ecohub Device",
    manufacturer: "Nob Ecohub",
    model: "Nob Ecohub local integration",
    serialNumber: 'nobo_hub-serial-1',
  },
  entities: [
    { id: 'status', name: 'Status', platform: "climate", state: true, attributes: { domain: "nobo_hub" } },
  ],
  online: true,
  updatedAt: '2026-01-01T00:00:00.000Z',
  source: 'manual',
};

tap.test('matches manual Nob Ecohub candidates and creates config flow output', async () => {
  const descriptor = createNoboHubDiscoveryDescriptor();
  const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'nobo_hub-manual-match');
  const result = await matcher!.matches({ source: 'manual', id: 'nobo_hub-device-1', name: "Nob Ecohub Device", metadata: { rawData } }, {});

  expect(result.matched).toBeTrue();
  expect(result.candidate?.integrationDomain).toEqual("nobo_hub");

  const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
  expect(validation.matched).toBeTrue();

  const done = await (await new NoboHubConfigFlow().start(result.candidate!, {})).submit!({});
  expect(done.kind).toEqual('done');
  expect(done.config?.uniqueId).toEqual('nobo_hub-device-1');
  expect(done.config?.rawData).toEqual(rawData);
});

tap.test('maps Nob Ecohub raw snapshots to runtime devices and entities', async () => {
  const client = new NoboHubClient({ name: "Nob Ecohub Runtime", rawData });
  const snapshot = await client.getSnapshot();
  const mappedSnapshot = NoboHubMapper.toSnapshotFromRaw({ name: "Nob Ecohub Runtime" }, rawData);
  const devices = NoboHubMapper.toDevices(mappedSnapshot);
  const entities = NoboHubMapper.toEntities(mappedSnapshot);

  expect(snapshot.online).toBeTrue();
  expect(mappedSnapshot.source).toEqual('manual');
  expect(devices[0].integrationDomain).toEqual("nobo_hub");
  expect(devices[0].manufacturer).toEqual("Nob Ecohub");
  expect(entities.some((entityArg) => entityArg.integrationDomain === "nobo_hub" && entityArg.platform === "climate")).toBeTrue();
});

tap.test('exposes Nob Ecohub runtime, HA alias, and unsupported control without executor', async () => {
  const integration = new NoboHubIntegration();
  const alias = new HomeAssistantNoboHubIntegration();
  expect(alias instanceof NoboHubIntegration).toBeTrue();
  expect(alias.domain).toEqual("nobo_hub");
  expect(integration.status).toEqual("control-runtime");
  expect(noboHubProfile.metadata.configFlow).toEqual(true);
  expect(noboHubProfile.metadata.requirements).toEqual([
    "pynobo==1.8.1",
  ]);

  const runtime = await integration.setup({ name: "Nob Ecohub Runtime", rawData }, {});
  const statusResult = await runtime.callService!({ domain: "nobo_hub", service: 'status', target: {} });
  const refresh = await runtime.callService!({ domain: "nobo_hub", service: 'refresh', target: {} });
  const snapshot = statusResult.data as INoboHubSnapshot;

  expect(statusResult.success).toBeTrue();
  expect(refresh.success).toBeTrue();
  expect(snapshot.online).toBeTrue();
  expect((await runtime.devices())[0].name).toEqual("Nob Ecohub Device");

  const command = await runtime.callService!({ domain: "nobo_hub", service: noboHubProfile.controlServices?.[0] || 'turn_on', target: {} });
  expect(command.success).toBeFalse();
  expect(command.error!).toContain('requires an injected client.execute() or commandExecutor');
  await runtime.destroy();
});

tap.test('reads and writes Nob Ecohub over the native TCP API', async () => {
  const commands: string[] = [];
  const server = createServer((socketArg) => {
    let buffer = '';
    socketArg.on('data', (chunkArg) => {
      buffer += chunkArg.toString('utf8');
      while (buffer.includes('\r')) {
        const index = buffer.indexOf('\r');
        const line = buffer.slice(0, index);
        buffer = buffer.slice(index + 1);
        commands.push(line);
        const [command] = line.split(' ');
        if (command === 'HELLO') {
          socketArg.write('HELLO 1.1\r');
          continue;
        }
        if (command === 'HANDSHAKE') {
          socketArg.write('HANDSHAKE\r');
          continue;
        }
        if (command === 'G00') {
          socketArg.write([
            'H00',
            'H01 1 Living 10 21 17 1 -1',
            'H02 234123456789 0 LivingSensor 0 1 -1 1',
            'Y02 234123456789 2150',
            'H03 10 Home 00001,12000,00001,12000,00001,12000,00001',
            'H05 123456789012 TestHub 0 -1 1.2.3 4.5 20240101',
          ].join('\r') + '\r');
          continue;
        }
        if (command === 'U00') {
          socketArg.write('V00 1 Living 10 22 17 1 -1\r');
          continue;
        }
        if (command === 'A03') {
          socketArg.write('B03 2 1 3 -1 -1 1 1\r');
        }
      }
    });
  });

  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 NoboHubClient({ host: '127.0.0.1', port, serial: '123456789012', timeoutMs: 1000 });
    const snapshot = await client.getSnapshot(true);
    const setTemperature = await client.execute({ domain: 'climate', service: 'set_temperature', target: { entityId: 'climate.zone_1_climate' }, data: { zoneId: '1', temperature: 22 } });
    const selectOverride = await client.execute({ domain: 'select', service: 'select_option', target: { entityId: 'select.zone_1_profile' }, data: { zoneId: '1', option: 'comfort' } });

    expect(snapshot.online).toBeTrue();
    expect(snapshot.source).toEqual('tcp');
    expect(snapshot.device.serialNumber).toEqual('123456789012');
    expect(snapshot.entities.find((entityArg) => entityArg.id === 'zone_1_climate')?.state).toEqual('auto');
    expect(snapshot.entities.find((entityArg) => entityArg.id === 'zone_1_temperature')?.state).toEqual(21.5);
    expect(setTemperature.success).toBeTrue();
    expect(selectOverride.success).toBeTrue();
    expect(commands.some((commandArg) => commandArg.startsWith('HELLO 1.1 123456789012 '))).toBeTrue();
    expect(commands.some((commandArg) => commandArg === 'G00')).toBeTrue();
    expect(commands.some((commandArg) => commandArg === 'U00 1 Living 10 22 17 1 -1')).toBeTrue();
    expect(commands.some((commandArg) => commandArg === 'A03 1 1 3 -1 -1 1 1')).toBeTrue();
  } finally {
    await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
  }
});

export default tap.start();
