/**
 * Function/Network/port-viewer.js
 * ---------------------------------------------------------------------------
 * NETWORK PORT VIEWER
 *
 * Active TCP connections and listening ports with the owning process.
 * Informational only - nothing is blocked, closed or terminated.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  '$procs = @{}',
  'Get-Process | ForEach-Object { $procs[$_.Id] = $_.ProcessName }',
  '$tcp = @(Get-NetTCPConnection | Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess)',
  '$udp = @(Get-NetUDPEndpoint | Select-Object LocalAddress, LocalPort, OwningProcess)',
  '$rows = @()',
  'foreach ($c in $tcp) { $rows += [ordered]@{ Protocol = "TCP"; LocalAddress = $c.LocalAddress; LocalPort = $c.LocalPort; RemoteAddress = $c.RemoteAddress; RemotePort = $c.RemotePort; State = $c.State.ToString(); PID = $c.OwningProcess; Process = $procs[[int]$c.OwningProcess] } }',
  'foreach ($u in $udp) { $rows += [ordered]@{ Protocol = "UDP"; LocalAddress = $u.LocalAddress; LocalPort = $u.LocalPort; RemoteAddress = $null; RemotePort = $null; State = "Listen"; PID = $u.OwningProcess; Process = $procs[[int]$u.OwningProcess] } }',
  '@($rows) | ConvertTo-Json -Depth 4 -Compress',
].join('\n');

async function collect() {
  const result = await system.runPowerShellJson(QUERY, { timeoutMs: 60000 });
  return result.ok ? system.asArray(result.data) : null;
}

const FILTERS = [
  { id: 'all', label: 'All connections and ports' },
  { id: 'listen', label: 'Listening ports only' },
  { id: 'established', label: 'Established connections only' },
  { id: 'remote', label: 'Connections to the internet' },
  { id: 'process', label: 'Filter by process name' },
  { id: 'port', label: 'Filter by port number' },
];

function isPrivate(address) {
  const text = String(address || '');
  return (
    text.startsWith('10.') ||
    text.startsWith('192.168.') ||
    text.startsWith('127.') ||
    text.startsWith('169.254.') ||
    text === '::1' ||
    text === '::' ||
    text === '0.0.0.0' ||
    /^172\.(1[6-9]|2\d|3[01])\./.test(text)
  );
}

function applyFilter(rows, filter, argument) {
  switch (filter) {
    case 'listen':
      return rows.filter((row) => String(row.State).toLowerCase() === 'listen');
    case 'established':
      return rows.filter((row) => String(row.State).toLowerCase() === 'established');
    case 'remote':
      return rows.filter((row) => row.RemoteAddress && !isPrivate(row.RemoteAddress));
    case 'process':
      return rows.filter((row) => String(row.Process || '').toLowerCase().includes(String(argument).toLowerCase()));
    case 'port':
      return rows.filter((row) => String(row.LocalPort) === String(argument) || String(row.RemotePort) === String(argument));
    default:
      return rows;
  }
}

function stateColor(ui, state) {
  const text = String(state).toLowerCase();
  if (text === 'established') return ui.c.brightGreen(state);
  if (text === 'listen') return ui.c.brightCyan(state);
  if (text.includes('wait')) return ui.c.gray(state);
  return ui.c.brightWhite(state);
}

module.exports = {
  name: 'Network Port Viewer',
  menuName: 'Port Viewer',
  description: 'Active TCP/UDP connections and listening ports with the owning process.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Network',
  order: 40,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'network:read'],
  tags: ['ports', 'connections', 'tcp', 'udp', 'netstat', 'network'],
  documentation:
    'Lists TCP connections and UDP endpoints with protocol, local address and port, remote address ' +
    'and port, state, process id and process name. Filters are available for listening ports, ' +
    'established connections, internet connections, a process name or a port number. ' +
    'Informational only - this function never closes a connection or stops a process. ' +
    'Some system-owned entries only show a process name when running as administrator.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const rows = await collect();
    return rows && rows.length
      ? { ok: true, message: `${rows.length} endpoint(s) enumerated` }
      : { ok: false, message: 'no connection data' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('Port Viewer', ['Active connections and listening ports.', 'Informational only.']);
    if (!kit.requireWindows('The port viewer')) return { ok: false, reason: 'unsupported-platform' };

    const spin = ui.spinner('Reading network connections...');
    const rows = await collect();
    spin.stop();
    if (!rows) return kit.noData('connection information');

    const listening = rows.filter((r) => String(r.State).toLowerCase() === 'listen');
    const established = rows.filter((r) => String(r.State).toLowerCase() === 'established');
    const external = rows.filter((r) => r.RemoteAddress && !isPrivate(r.RemoteAddress));

    for (;;) {
      ui.blank();
      ui.section('SUMMARY');
      ui.blank();
      kit.kv('Total Endpoints', system.formatNumber(rows.length));
      kit.kv('Listening Ports', system.formatNumber(listening.length));
      kit.kv('Established Connections', system.formatNumber(established.length));
      kit.kv('Internet Connections', system.formatNumber(external.length));
      if (!ctx.isAdmin) {
        ui.println('  ' + ui.c.gray('Some process names need administrator rights to be shown.'));
      }

      ui.blank();
      const choice = await prompt.choose('Which view?', FILTERS.map((f) => f.label));
      if (choice === null) break;

      const filter = FILTERS[choice];
      let argument = null;
      if (filter.id === 'process') {
        argument = await prompt.text('Process name contains');
        if (!argument) continue;
      }
      if (filter.id === 'port') {
        argument = await prompt.number('Port number', 1, 65535);
        if (argument === null) continue;
      }

      const filtered = applyFilter(rows, filter.id, argument).sort(
        (a, b) => Number(a.LocalPort) - Number(b.LocalPort)
      );

      ui.blank();
      ui.section(filter.label.toUpperCase());
      ui.blank();

      if (!filtered.length) {
        ui.println('  ' + ui.c.gray('Nothing matches this view.'));
      } else {
        kit.table(
          ['PROTO', 'LOCAL', 'PORT', 'REMOTE', 'STATE', 'PID', 'PROCESS'],
          filtered.map((row) => [
            ui.c.gray(row.Protocol),
            ui.c.brightWhite(ui.truncate(String(row.LocalAddress || '*'), 15)),
            ui.c.brightYellow(String(row.LocalPort)),
            ui.c.gray(
              row.RemoteAddress && String(row.RemoteAddress) !== '0.0.0.0'
                ? `${ui.truncate(String(row.RemoteAddress), 15)}:${row.RemotePort}`
                : '-'
            ),
            stateColor(ui, row.State),
            ui.c.gray(String(row.PID || '-')),
            ui.c.brightCyan(ui.truncate(String(row.Process || '-'), 16)),
          ]),
          { align: ['left', 'left', 'right', 'left', 'left', 'right', 'left'], limit: 30 }
        );
      }

      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Choose another view' },
          { key: '2', label: 'Export this view as a TXT report' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'PORT VIEWER' }
      );
      ui.blank();

      const answer = await prompt.select();
      if (!answer && prompt.isClosed()) break;
      if (answer === '0') break;
      if (answer === '2') {
        const report = kit.report('Network Port Report');
        const summary = report.section('SUMMARY');
        summary.add('View', filter.label);
        summary.add('Total Endpoints', system.formatNumber(rows.length));
        summary.add('Listening Ports', system.formatNumber(listening.length));
        summary.add('Established Connections', system.formatNumber(established.length));

        const list = report.section('CONNECTIONS');
        filtered.forEach((row, index) => {
          list.add(
            `${index + 1}. ${row.Protocol} ${row.LocalAddress}:${row.LocalPort}`,
            [
              row.RemoteAddress ? `-> ${row.RemoteAddress}:${row.RemotePort}` : null,
              row.State,
              row.PID ? `PID ${row.PID}` : null,
              row.Process || null,
            ]
              .filter(Boolean)
              .join('  ')
          );
        });
        kit.saveReport(report, 'PortViewer');
        await prompt.pause();
      }
    }

    return kit.done('Port viewer closed.');
  },
};
