import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { LivisiClient, LivisiConfigFlow, LivisiIntegration, LivisiMapper, createLivisiDiscoveryDescriptor, livisiProfile, type ILivisiSnapshot, type TLivisiRawData } from '../../ts/integrations/livisi/index.js';

const rawData: TLivisiRawData = {
  device: {
    id: 'livisi-device-1',
    name: "LIVISI Smart Home Device",
    manufacturer: "LIVISI Smart Home",
    model: "LIVISI Smart Home local integration",
    serialNumber: 'livisi-serial-1',
  },
  entities: [
    { id: 'status', name: 'Status', platform: "binary_sensor", state: true, attributes: { domain: "livisi" } },
  ],
  online: true,
  updatedAt: '2026-01-01T00:00:00.000Z',
  source: 'manual',
};

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

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

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

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

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

  expect(snapshot.online).toBeTrue();
  expect(mappedSnapshot.source).toEqual('manual');
  expect(devices[0].integrationDomain).toEqual("livisi");
  expect(devices[0].manufacturer).toEqual("LIVISI Smart Home");
  expect(entities.some((entityArg) => entityArg.integrationDomain === "livisi" && entityArg.platform === "binary_sensor")).toBeTrue();
});

tap.test('polls LIVISI SHC over local HTTP and sends action commands only after auth', async () => {
  const requests: Array<{ method?: string; url?: string; auth?: string; body?: unknown }> = [];
  let actionPayload: Record<string, unknown> | undefined;
  const server = createServer((requestArg, responseArg) => {
    void handleLivisiRequest(requestArg, responseArg, requests, (payloadArg) => {
      actionPayload = payloadArg;
    });
  });

  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 runtime = await new LivisiIntegration().setup({ host: '127.0.0.1', port, password: 'secret', timeoutMs: 1000 }, {});
    const entities = await runtime.entities();
    const command = await runtime.callService!({ domain: 'switch', service: 'turn_off', target: {}, data: { capabilityId: 'cap-switch' } });

    expect(requests.some((requestArg) => requestArg.url === '/auth/token' && requestArg.auth === 'Basic Y2xpZW50SWQ6Y2xpZW50UGFzcw==')).toBeTrue();
    expect(requests.some((requestArg) => requestArg.url === '/status' && requestArg.auth === 'Bearer token-1')).toBeTrue();
    expect(entities.some((entityArg) => entityArg.platform === 'switch' && entityArg.state === true)).toBeTrue();
    expect(entities.some((entityArg) => entityArg.platform === 'binary_sensor' && entityArg.state === false && entityArg.attributes?.deviceClass === 'door')).toBeTrue();
    expect(entities.some((entityArg) => entityArg.platform === 'climate' && (entityArg.state as { targetTemperature?: number }).targetTemperature === 21)).toBeTrue();
    expect(command.success).toBeTrue();
    expect(actionPayload?.target).toEqual('/capability/cap-switch');
    expect((actionPayload?.params as { onState?: { value?: boolean } }).onState?.value).toBeFalse();
    await runtime.destroy();
  } finally {
    await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
  }
});

tap.test('exposes LIVISI Smart Home runtime and unsupported control without executor', async () => {
  const integration = new LivisiIntegration();
  expect(integration.domain).toEqual("livisi");
  expect(integration.status).toEqual("control-runtime");
  expect(livisiProfile.metadata.configFlow).toEqual(true);
  expect(livisiProfile.metadata.requirements).toEqual([
    "livisi==0.0.25",
  ]);

  const runtime = await integration.setup({ name: "LIVISI Smart Home Runtime", rawData }, {});
  const statusResult = await runtime.callService!({ domain: "livisi", service: 'status', target: {} });
  const refresh = await runtime.callService!({ domain: "livisi", service: 'refresh', target: {} });
  const snapshot = statusResult.data as ILivisiSnapshot;

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

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

const handleLivisiRequest = async (
  requestArg: IncomingMessage,
  responseArg: ServerResponse,
  requestsArg: Array<{ method?: string; url?: string; auth?: string; body?: unknown }>,
  onActionArg: (payloadArg: Record<string, unknown>) => void,
): Promise<void> => {
  const bodyText = await readRequestBody(requestArg);
  const body = bodyText ? JSON.parse(bodyText) as unknown : undefined;
  requestsArg.push({ method: requestArg.method, url: requestArg.url, auth: requestArg.headers.authorization, body });

  if (requestArg.url === '/auth/token' && requestArg.method === 'POST') {
    json(responseArg, { access_token: 'token-1' });
    return;
  }

  if (requestArg.headers.authorization !== 'Bearer token-1') {
    json(responseArg, { errorcode: 2009 }, 401);
    return;
  }

  if (requestArg.url === '/status') {
    json(responseArg, { controllerType: 'Avatar', serialNumber: 'SHC-123' });
    return;
  }

  if (requestArg.url === '/device') {
    json(responseArg, [
      { id: 'switch-1', type: 'PSS', manufacturer: 'Livisi', config: { name: 'Coffee Plug' }, location: '/location/room-1' },
      { id: 'door-1', type: 'WDS', manufacturer: 'Livisi', config: { name: 'Front Door' }, tags: { typeCategory: 'TCDoorId' }, location: '/location/room-1' },
      { id: 'climate-1', type: 'VRCC', manufacturer: 'Livisi', config: { name: 'Climate' }, location: '/location/room-1' },
    ]);
    return;
  }

  if (requestArg.url === '/capability') {
    json(responseArg, [
      { id: 'cap-switch', type: 'SwitchActuator', device: '/device/switch-1' },
      { id: 'cap-door', type: 'WindowDoorSensor', device: '/device/door-1' },
      { id: 'cap-setpoint', type: 'RoomSetpoint', device: '/device/climate-1', config: { minTemperature: 6, maxTemperature: 30 } },
      { id: 'cap-temperature', type: 'RoomTemperature', device: '/device/climate-1' },
      { id: 'cap-humidity', type: 'RoomHumidity', device: '/device/climate-1' },
    ]);
    return;
  }

  if (requestArg.url === '/location') {
    json(responseArg, [{ id: 'room-1', config: { name: 'Hallway' } }]);
    return;
  }

  if (requestArg.url === '/capability/cap-switch/state') {
    json(responseArg, { onState: { value: true } });
    return;
  }

  if (requestArg.url === '/capability/cap-door/state') {
    json(responseArg, { isOpen: { value: false } });
    return;
  }

  if (requestArg.url === '/capability/cap-setpoint/state') {
    json(responseArg, { setpointTemperature: { value: 21 } });
    return;
  }

  if (requestArg.url === '/capability/cap-temperature/state') {
    json(responseArg, { temperature: { value: 20.5 } });
    return;
  }

  if (requestArg.url === '/capability/cap-humidity/state') {
    json(responseArg, { humidity: { value: 45 } });
    return;
  }

  if (requestArg.url === '/action' && requestArg.method === 'POST') {
    onActionArg(body as Record<string, unknown>);
    json(responseArg, { resultCode: 'Success' });
    return;
  }

  json(responseArg, { error: 'not found' }, 404);
};

const readRequestBody = async (requestArg: IncomingMessage): Promise<string> => new Promise((resolve, reject) => {
  const chunks: Buffer[] = [];
  requestArg.on('data', (chunkArg) => chunks.push(Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)));
  requestArg.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
  requestArg.on('error', reject);
});

const json = (responseArg: ServerResponse, dataArg: unknown, statusArg = 200): void => {
  responseArg.statusCode = statusArg;
  responseArg.setHeader('content-type', 'application/json');
  responseArg.end(JSON.stringify(dataArg));
};

export default tap.start();
