import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../ts/plugins.js';
import { HomeAssistantPioneerIntegration, PioneerClient, PioneerConfigFlow, PioneerIntegration, PioneerMapper, createPioneerDiscoveryDescriptor, pioneerProfile, type IPioneerSnapshot, type TPioneerRawData } from '../../ts/integrations/pioneer/index.js';

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

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

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

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

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

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

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

tap.test('polls and controls Pioneer through native Telnet TCP', async () => {
  const server = await startPioneerTestServer();
  try {
    const client = new PioneerClient({ host: '127.0.0.1', port: server.port, timeoutMs: 500, commandTimeoutMs: 50, sourceScanLimit: 2 });
    const snapshot = await client.getSnapshot(true);
    const entity = snapshot.entities.find((entityArg) => entityArg.id === 'media_player');

    expect(snapshot.source).toEqual('tcp');
    expect(snapshot.device.protocol).toEqual('telnet');
    expect(entity?.state).toEqual('on');
    expect(entity?.attributes?.volume).toEqual(92);
    expect(entity?.attributes?.isVolumeMuted).toBeTrue();
    expect(entity?.attributes?.source).toEqual('DVD');

    const result = await client.execute({ domain: 'media_player', service: 'volume_down', target: {} });
    expect(result.success).toBeTrue();
    expect(server.received.includes('VD')).toBeTrue();
  } finally {
    await server.close();
  }
});

tap.test('exposes Pioneer runtime, HA alias, and unsupported control without executor', async () => {
  const integration = new PioneerIntegration();
  const alias = new HomeAssistantPioneerIntegration();
  expect(alias instanceof PioneerIntegration).toBeTrue();
  expect(alias.domain).toEqual("pioneer");
  expect(integration.status).toEqual("control-runtime");
  expect(pioneerProfile.metadata.configFlow).toEqual(false);
  expect(pioneerProfile.metadata.requirements).toEqual([]);

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

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

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

interface IPioneerTestServer {
  port: number;
  received: string[];
  close(): Promise<void>;
}

async function startPioneerTestServer(): Promise<IPioneerTestServer> {
  const received: string[] = [];
  const sockets = new Set<plugins.net.Socket>();
  const server = plugins.net.createServer((socket) => {
    sockets.add(socket);
    socket.once('close', () => sockets.delete(socket));
    let buffer = '';
    socket.on('data', (chunkArg) => {
      buffer += Buffer.from(chunkArg).toString('ascii');
      let index = buffer.indexOf('\r');
      while (index >= 0) {
        const command = buffer.slice(0, index).trim();
        buffer = buffer.slice(index + 1);
        if (command) {
          received.push(command);
          const response = pioneerResponse(command);
          if (response) {
            socket.write(`${response}\r\n`, 'ascii');
          }
        }
        index = buffer.indexOf('\r');
      }
    });
  });

  await new Promise<void>((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      server.off('error', reject);
      resolve();
    });
  });

  const address = server.address();
  if (!address || typeof address === 'string') {
    throw new Error('Pioneer test server did not bind to a TCP port.');
  }

  return {
    port: address.port,
    received,
    close: async () => {
      for (const socket of sockets) {
        socket.destroy();
      }
      await new Promise<void>((resolve) => server.close(() => resolve()));
    },
  };
}

function pioneerResponse(commandArg: string): string | undefined {
  const responses: Record<string, string> = {
    '?P': 'PWR0',
    '?V': 'VOL092',
    '?M': 'MUT0',
    '?RGB00': 'RGB00DVD',
    '?RGB01': 'RGB01TUNER',
    '?F': 'FN00',
  };
  return responses[commandArg];
}

export default tap.start();
