import { spawn, type ChildProcess } from 'child_process';

export interface GatewayDiscoveryConfig {
  serviceName: string;
  serviceType: string;
  port: number;
  txtRecord?: Record<string, string>;
}

export interface GatewayDiscoveryHandle {
  stop(): Promise<void>;
}

function toTxtArgs(txtRecord?: Record<string, string>): string[] {
  if (!txtRecord) {
    return [];
  }
  return Object.entries(txtRecord).map(([key, value]) => `${key}=${value}`);
}

async function spawnAdvertiser(command: string, args: string[]): Promise<ChildProcess> {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, { stdio: 'ignore' });

    const onError = (error: Error): void => {
      cleanup();
      reject(error);
    };
    const onExit = (code: number | null, signal: NodeJS.Signals | null): void => {
      cleanup();
      reject(new Error(`${command} exited early (code=${code ?? 'null'}, signal=${signal ?? 'null'})`));
    };
    const onSpawn = (): void => {
      setTimeout(() => {
        cleanup();
        resolve(child);
      }, 100);
    };
    const cleanup = (): void => {
      child.off('error', onError);
      child.off('exit', onExit);
      child.off('spawn', onSpawn);
    };

    child.once('error', onError);
    child.once('exit', onExit);
    child.once('spawn', onSpawn);
  });
}

async function stopChild(child: ChildProcess): Promise<void> {
  if (child.exitCode !== null || child.killed) {
    return;
  }

  await new Promise<void>((resolve) => {
    const timeout = setTimeout(() => {
      child.kill('SIGKILL');
      resolve();
    }, 1000);

    child.once('exit', () => {
      clearTimeout(timeout);
      resolve();
    });

    child.kill('SIGTERM');
  });
}

/**
 * Starts LAN discovery using best-effort host tools.
 * Priority: avahi-publish-service (Linux) -> dns-sd (macOS).
 */
export async function startGatewayDiscovery(config: GatewayDiscoveryConfig): Promise<GatewayDiscoveryHandle> {
  const txtArgs = toTxtArgs(config.txtRecord);
  const attempts: Array<{ command: string; args: string[] }> = [
    {
      command: 'avahi-publish-service',
      args: [config.serviceName, config.serviceType, String(config.port), ...txtArgs],
    },
    {
      command: 'dns-sd',
      args: ['-R', config.serviceName, config.serviceType, 'local', String(config.port), ...txtArgs],
    },
  ];

  let lastError: Error | null = null;

  for (const attempt of attempts) {
    try {
      const child = await spawnAdvertiser(attempt.command, attempt.args);
      child.unref();
      return {
        stop: async () => {
          await stopChild(child);
        },
      };
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));
    }
  }

  throw new Error(
    `Failed to start mDNS advertiser (tried avahi-publish-service and dns-sd): ${lastError?.message ?? 'unknown error'}`,
  );
}
