/**
 * Function/Network/network-scanner.js
 * ---------------------------------------------------------------------------
 * LOCAL NETWORK SCANNER
 *
 * Finds reachable devices on your own subnet with an ICMP sweep and reads the
 * MAC addresses from the local ARP table.
 *
 * Deliberately conservative: ICMP echo and name resolution only. It never
 * probes ports, never authenticates and never touches a device in any way -
 * it is limited to the local subnet of this machine.
 * ---------------------------------------------------------------------------
 */

'use strict';

const os = require('os');
const kit = require('../../core/kit');
const system = require('../../core/system');

/** Local IPv4 interfaces with a /24-compatible subnet. */
function localSubnets() {
  const found = [];
  for (const [name, entries] of Object.entries(os.networkInterfaces())) {
    for (const entry of entries || []) {
      if (entry.internal || entry.family !== 'IPv4') continue;
      const parts = entry.address.split('.');
      if (parts.length !== 4) continue;
      found.push({
        adapter: name,
        address: entry.address,
        netmask: entry.netmask,
        mac: entry.mac,
        prefix: `${parts[0]}.${parts[1]}.${parts[2]}`,
      });
    }
  }
  return found;
}

/**
 * Ping every host of a /24 in parallel, then read the ARP table.
 * The whole sweep happens inside one PowerShell process for speed.
 */
function buildScript(prefix, timeoutMs) {
  return [
    '$ErrorActionPreference = "SilentlyContinue"',
    `$prefix = "${prefix}"`,
    `$timeout = ${timeoutMs}`,
    '$tasks = @()',
    'foreach ($i in 1..254) {',
    '  $ip = "$prefix.$i"',
    '  $ping = New-Object System.Net.NetworkInformation.Ping',
    '  $tasks += [ordered]@{ Ip = $ip; Task = $ping.SendPingAsync($ip, $timeout) }',
    '}',
    '[System.Threading.Tasks.Task]::WaitAll(@($tasks | ForEach-Object { $_.Task }))',
    '$alive = @()',
    'foreach ($t in $tasks) {',
    '  $reply = $t.Task.Result',
    '  if ($reply -and $reply.Status -eq "Success") {',
    '    $alive += [ordered]@{ Ip = $t.Ip; Time = $reply.RoundtripTime }',
    '  }',
    '}',
    '$arp = @{}',
    'foreach ($line in (arp -a)) {',
    '  if ($line -match "^\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s+([0-9a-fA-F-]{11,})\\s+(\\w+)") {',
    '    $arp[$matches[1]] = $matches[2]',
    '  }',
    '}',
    '$result = @()',
    // NOTE: $host is a reserved automatic variable in PowerShell - never use it
    // as a loop variable, the assignment fails at runtime.
    'foreach ($device in $alive) {',
    '  $name = $null',
    '  try { $name = [System.Net.Dns]::GetHostEntry($device.Ip).HostName } catch { }',
    '  $result += [ordered]@{ Ip = $device.Ip; Time = $device.Time; Mac = $arp[$device.Ip]; Host = $name }',
    '}',
    '@($result) | ConvertTo-Json -Depth 4 -Compress',
  ].join('\n');
}

async function sweep(prefix, timeoutMs) {
  const result = await system.runPowerShellJson(buildScript(prefix, timeoutMs), { timeoutMs: 180000 });
  return result.ok ? system.asArray(result.data) : null;
}

module.exports = {
  name: 'Local Network Scanner',
  menuName: 'Local Network Scanner',
  description: 'Finds reachable devices on your own subnet with an ICMP sweep and the ARP table.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Network',
  order: 60,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['network:icmp', 'network:arp'],
  tags: ['network', 'scan', 'devices', 'lan', 'discovery'],
  config: { timeoutMs: 400 },
  documentation:
    'Sends one ICMP echo request to every address of your own /24 subnet, then reads the local ARP ' +
    'cache for the MAC addresses and resolves host names. Only your own subnet can be scanned. ' +
    'No port is probed, no authentication is attempted and nothing is sent to the discovered ' +
    'devices beyond the echo request - this is a reachability check, nothing more.',

  async selfTest() {
    const subnets = localSubnets();
    return subnets.length
      ? { ok: true, message: `${subnets.length} local subnet(s) available` }
      : { ok: false, message: 'no local IPv4 subnet' };
  },

  async execute(ctx) {
    const { ui, prompt, settings, config } = ctx;
    kit.header('Local Network Scanner', [
      'Finds devices that answer on your own subnet.',
      'Reachability only - no ports are probed.',
    ]);
    if (!kit.requireWindows('The network scanner')) return { ok: false, reason: 'unsupported-platform' };

    const subnets = localSubnets();
    if (!subnets.length) {
      ui.blank();
      ui.warning('This machine has no active IPv4 network connection.');
      return { ok: false, reason: 'no-network' };
    }

    const choice = await prompt.choose(
      'Which network should be scanned?',
      subnets.map((net) => ({ label: `${net.prefix}.0/24`, hint: `${net.adapter} - this PC is ${net.address}` }))
    );
    if (choice === null) return kit.done('Scan cancelled.');

    const subnet = subnets[choice];
    const timeoutMs = Number(settings.tools.networkScanTimeoutMs) || Number(config.timeoutMs) || 400;

    ui.blank();
    ui.section('SCAN');
    ui.blank();
    kit.kv('Network', `${subnet.prefix}.0/24`);
    kit.kv('This Computer', `${subnet.address}  (${subnet.adapter})`);
    kit.kv('Addresses To Probe', '254');
    kit.kv('Timeout Per Host', `${timeoutMs} ms`);
    ui.blank();

    const started = Date.now();
    const spin = ui.spinner('Sweeping the subnet - this takes a few seconds...');
    const devices = await sweep(subnet.prefix, timeoutMs);
    spin.stop();

    if (!devices) {
      ui.error('The scan could not be executed on this system.');
      return { ok: false, reason: 'scan-failed' };
    }

    const sorted = devices.sort((a, b) => {
      const left = Number(String(a.Ip).split('.')[3]);
      const right = Number(String(b.Ip).split('.')[3]);
      return left - right;
    });

    ui.blank();
    ui.section('DEVICES FOUND');
    ui.blank();

    if (!sorted.length) {
      ui.println('  ' + ui.c.gray('No device answered. Many devices ignore ICMP by default.'));
    } else {
      kit.table(
        ['IP ADDRESS', 'HOST NAME', 'MAC ADDRESS', 'PING'],
        sorted.map((device) => [
          (String(device.Ip) === subnet.address ? ui.c.brightGreen : ui.c.brightWhite)(String(device.Ip)),
          ui.c.gray(ui.truncate(system.clean(device.Host) || '-', 26)),
          ui.c.brightCyan(system.clean(device.Mac) || '-'),
          ui.c.gray(`${device.Time} ms`),
        ]),
        { align: ['left', 'left', 'left', 'right'], limit: 40 }
      );
    }

    ui.blank();
    kit.kv('Devices Reachable', String(sorted.length), 'brightGreen');
    kit.kv('Scan Duration', `${((Date.now() - started) / 1000).toFixed(1)}s`);
    ui.blank();
    ui.println('  ' + ui.c.gray('Devices that block ICMP do not appear here even though they are online.'));

    await kit.offerExport('Local Network Scan', 'NetworkScan', (report) => {
      const section = report.section('SCAN');
      section.add('Network', `${subnet.prefix}.0/24`);
      section.add('This Computer', `${subnet.address} (${subnet.adapter})`);
      section.add('Devices Reachable', String(sorted.length));
      section.add('Scan Duration', `${((Date.now() - started) / 1000).toFixed(1)}s`);

      const list = report.section('DEVICES');
      sorted.forEach((device, index) => {
        const group = list.group(`${index + 1}. ${device.Ip}`);
        group.add('Host Name', system.clean(device.Host));
        group.add('MAC Address', system.clean(device.Mac));
        group.add('Response Time', `${device.Time} ms`);
      });
    });

    return kit.done(`${sorted.length} device(s) found.`);
  },
};
