import * as http from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DlinkHnapSmartPlugClient, DlinkIntegration, type IDlinkCommand } from '../../ts/integrations/dlink/index.js';

tap.test('does not fake switch command success without host/client/executor', async () => {
  const runtime = await new DlinkIntegration().setup({
    name: 'Kitchen Plug',
    state: 'OFF',
    nativeHttpEnabled: false,
  }, {});

  const result = await runtime.callService?.({
    domain: 'switch',
    service: 'turn_on',
    target: { entityId: 'switch.kitchen_plug' },
    data: {},
  });

  expect(result?.success).toBeFalse();
  expect(result?.error || '').toInclude('require host/password');
  await runtime.destroy();
});

tap.test('delegates D-Link switch commands to an injected executor', async () => {
  let command: IDlinkCommand | undefined;
  const runtime = await new DlinkIntegration().setup({
    name: 'Kitchen Plug',
    state: 'OFF',
    nativeHttpEnabled: false,
    commandExecutor: async (commandArg) => {
      command = commandArg;
      return { success: true, transmitted: true, data: { accepted: true } };
    },
  }, {});

  const result = await runtime.callService?.({
    domain: 'switch',
    service: 'turn_on',
    target: { entityId: 'switch.kitchen_plug' },
    data: {},
  });

  expect(result?.success).toBeTrue();
  expect(command?.type).toEqual('switch_turn_on');
  expect(command?.state).toEqual('ON');

  const snapshotResult = await runtime.callService?.({ domain: 'dlink', service: 'snapshot', target: {}, data: {} });
  const snapshot = snapshotResult?.data as { switch?: { state?: string } } | undefined;
  expect(snapshot?.switch?.state).toEqual('ON');
  await runtime.destroy();
});

tap.test('reads and commands a local HNAP smart plug over native HTTP', async () => {
  const requests: string[] = [];
  let requestedOff = false;
  const server = http.createServer(async (request, response) => {
    const body = await readRequestBody(request);
    requests.push(body);
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/xml; charset=utf-8');

    if (body.includes('<Action>request</Action>')) {
      response.end(hnapResponse('Login', '<Challenge>challenge</Challenge><Cookie>cookie</Cookie><PublicKey>public</PublicKey>'));
      return;
    }
    if (body.includes('<Action>login</Action>')) {
      response.end(hnapResponse('Login', '<LoginResult>success</LoginResult>'));
      return;
    }
    if (body.includes('<GetDeviceSettings')) {
      response.end(hnapResponse('GetDeviceSettings', '<ModelName>DSP-W215</ModelName>'));
      return;
    }
    if (body.includes('<GetSocketSettings')) {
      response.end(hnapResponse('GetSocketSettings', '<OPStatus>true</OPStatus>'));
      return;
    }
    if (body.includes('<GetCurrentTemperature')) {
      response.end(hnapResponse('GetCurrentTemperature', '<CurrentTemperature>31</CurrentTemperature>'));
      return;
    }
    if (body.includes('<GetCurrentPowerConsumption')) {
      response.end(hnapResponse('GetCurrentPowerConsumption', '<CurrentConsumption>5.6</CurrentConsumption>'));
      return;
    }
    if (body.includes('<GetPMWarningThreshold')) {
      response.end(hnapResponse('GetPMWarningThreshold', '<TotalConsumption>42.5</TotalConsumption>'));
      return;
    }
    if (body.includes('<SetSocketSettings')) {
      requestedOff = body.includes('<OPStatus>false</OPStatus>');
      response.end(hnapResponse('SetSocketSettings', '<SetSocketSettingsResult>OK</SetSocketSettingsResult>'));
      return;
    }

    response.statusCode = 500;
    response.end('unexpected action');
  });

  await listen(server);
  const address = server.address();
  const port = typeof address === 'object' && address ? address.port : 0;
  try {
    const client = new DlinkHnapSmartPlugClient({
      host: `127.0.0.1:${port}`,
      password: '123456',
      timeoutMs: 1000,
    });

    const snapshot = await client.readSnapshot();
    expect(snapshot.connected).toBeTrue();
    expect(snapshot.device.modelName).toEqual('DSP-W215');
    expect(snapshot.switch.state).toEqual('ON');
    expect(snapshot.switch.temperature).toEqual(31);
    expect(snapshot.switch.currentConsumption).toEqual(5.6);
    expect(snapshot.switch.totalConsumption).toEqual(42.5);

    const commandResult = await client.setState('OFF');
    expect(commandResult).toEqual('OK');
    expect(requestedOff).toBeTrue();
    expect(requests.some((requestArg) => requestArg.includes('<SetSocketSettings'))).toBeTrue();
  } finally {
    await close(server);
  }
});

const hnapResponse = (actionArg: string, innerXmlArg: string): string => `<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><${actionArg}Response xmlns="http://purenetworks.com/HNAP1/">${innerXmlArg}</${actionArg}Response></soap:Body></soap:Envelope>`;

const readRequestBody = async (requestArg: http.IncomingMessage): Promise<string> => {
  const chunks: Buffer[] = [];
  for await (const chunk of requestArg) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  }
  return Buffer.concat(chunks).toString('utf8');
};

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();
