const http = require('http');
const os = require('os');
const path = require('path');
const { randomBytes, timingSafeEqual } = require('crypto');
const { execFile } = require('child_process');
const { readdir, rm, stat } = require('fs/promises');
const { runHealthScan } = require('./health-scan');

const host = '127.0.0.1';
const port = 37879;
const scannerSessionToken = randomBytes(32).toString('base64url');
const allowedOrigins = [
  /^https:\/\/oncalltechsa\.co\.za$/i,
  /^https:\/\/www\.oncalltechsa\.co\.za$/i,
  /^https:\/\/oncalltechsa\.netlify\.app$/i,
  /^https:\/\/[a-z0-9-]+--oncalltechsa\.netlify\.app$/i,
  ...(process.env.ONCALL_ALLOW_LOCAL_DEV_ORIGINS === 'true' ? [/^http:\/\/localhost:\d+$/i, /^http:\/\/127\.0\.0\.1:\d+$/i] : [])
];

function isAllowedOrigin(origin) {
  return Boolean(origin) && origin !== 'null' && allowedOrigins.some((pattern) => pattern.test(origin));
}

function isLoopbackRequest(req) {
  const address = req.socket?.remoteAddress || '';
  return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
}

function safeCompare(left, right) {
  const a = Buffer.from(String(left || ''));
  const b = Buffer.from(String(right || ''));
  return a.length === b.length && timingSafeEqual(a, b);
}

function hasScannerToken(req) {
  return safeCompare(req.headers['x-oncall-scanner-token'], scannerSessionToken);
}

function sendJson(res, statusCode, payload, origin) {
  res.writeHead(statusCode, {
    'content-type': 'application/json; charset=utf-8',
    'access-control-allow-origin': isAllowedOrigin(origin) ? origin : 'null',
    'access-control-allow-methods': 'GET,POST,OPTIONS',
    'access-control-allow-headers': 'content-type,x-oncall-scanner-token',
    'access-control-allow-private-network': 'true',
    'cache-control': 'no-store',
    'content-security-policy': "default-src 'none'; frame-ancestors 'none'",
    'cross-origin-resource-policy': 'cross-origin',
    'referrer-policy': 'no-referrer',
    'vary': 'Origin',
    'x-content-type-options': 'nosniff'
  });
  res.end(JSON.stringify(payload));
}

function sendPreflight(res, origin) {
  if (!isAllowedOrigin(origin)) {
    res.writeHead(403, {
      'content-type': 'application/json; charset=utf-8',
      'cache-control': 'no-store',
      'x-content-type-options': 'nosniff'
    });
    return res.end(JSON.stringify({ ok: false, error: 'Origin is not allowed.' }));
  }
  res.writeHead(204, {
    'access-control-allow-origin': origin,
    'access-control-allow-methods': 'GET,POST,OPTIONS',
    'access-control-allow-headers': 'content-type,x-oncall-scanner-token',
    'access-control-allow-private-network': 'true',
    'access-control-max-age': '600',
    'cache-control': 'no-store',
    'vary': 'Origin'
  });
  res.end();
}

function readBody(req) {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', (chunk) => {
      body += chunk;
      if (body.length > 4096) {
        reject(new Error('Request body too large.'));
        req.destroy();
      }
    });
    req.on('end', () => {
      if (!body) return resolve({});
      try {
        resolve(JSON.parse(body));
      } catch {
        reject(new Error('Invalid JSON body.'));
      }
    });
    req.on('error', reject);
  });
}

function runCommand(command, args = [], timeout = 15000) {
  return new Promise((resolve) => {
    execFile(command, args, { timeout, windowsHide: true }, (error, stdout, stderr) => {
      resolve({
        ok: !error,
        command: [command, ...args].join(' '),
        output: String(stdout || '').trim(),
        error: String(stderr || error?.message || '').trim()
      });
    });
  });
}

async function folderSize(target) {
  try {
    const info = await stat(target);
    if (!info.isDirectory()) return info.size;
    const entries = await readdir(target, { withFileTypes: true });
    let total = 0;
    for (const entry of entries) {
      total += await folderSize(path.join(target, entry.name));
    }
    return total;
  } catch {
    return 0;
  }
}

function bytesToGb(bytes) {
  return Math.round((Number(bytes || 0) / 1024 / 1024 / 1024) * 10) / 10;
}

async function cleanDirectoryContents(target) {
  const before = await folderSize(target);
  let removed = 0;
  let skipped = 0;
  try {
    const entries = await readdir(target, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(target, entry.name);
      try {
        await rm(fullPath, { recursive: true, force: true, maxRetries: 2 });
        removed += 1;
      } catch {
        skipped += 1;
      }
    }
  } catch {
    skipped += 1;
  }
  const after = await folderSize(target);
  return { target, removed, skipped, beforeGb: bytesToGb(before), afterGb: bytesToGb(after), freedGb: Math.max(0, bytesToGb(before - after)) };
}

function oncallSupportDataTargets() {
  const home = os.homedir();
  const appData = process.platform === 'win32'
    ? process.env.APPDATA || path.join(home, 'AppData', 'Roaming')
    : path.join(home, 'Library', 'Application Support');
  const cacheDir = process.platform === 'win32'
    ? process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local')
    : path.join(home, 'Library', 'Caches');
  return [
    path.join(appData, 'OnCallTechSA Support App'),
    path.join(appData, 'oncalltechsa-support-app'),
    path.join(cacheDir, 'OnCallTechSA Support App'),
    path.join(cacheDir, 'oncalltechsa-support-app'),
    path.join(os.tmpdir(), 'oncalltechsa'),
    path.join(os.tmpdir(), 'oncalltechsa-support-app')
  ];
}

async function repairAction(action) {
  if (action === 'fix-security-baseline') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process windowsdefender:'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension']);
    return {
      ok: result.ok,
      action,
      title: 'Security fix assistant opened',
      detail: result.ok
        ? 'Review the flagged security controls, enable the recommended protection, then run Rescan after fix. OnCallTechSA does not store or request your admin password.'
        : result.error,
      command: result.command,
      nextSteps: process.platform === 'win32'
        ? [
            'Turn on Microsoft Defender real-time protection if it is disabled.',
            'Open Firewall & network protection and enable firewall profiles.',
            'Open BitLocker Drive Encryption if device encryption is flagged.',
            'Restart if Windows Update or security settings require it, then rescan.'
          ]
        : [
            'Enable FileVault if encryption is flagged.',
            'Enable Firewall if firewall is flagged.',
            'Keep Gatekeeper enabled for downloaded app protection.',
            'SIP changes require Recovery Mode and should be specialist-approved.',
            'Rescan after each change.'
          ]
    };
  }

  if (action === 'open-firewall-settings') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process wf.msc'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension']);
    return { ok: result.ok, action, title: 'Firewall settings opened', detail: result.ok ? 'Enable firewall protection, approve any OS prompt, then rescan.' : result.error, command: result.command };
  }

  if (action === 'open-encryption-settings') {
    const result = process.platform === 'win32'
      ? await runCommand('control.exe', ['/name', 'Microsoft.BitLockerDriveEncryption'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension']);
    return { ok: result.ok, action, title: process.platform === 'win32' ? 'BitLocker settings opened' : 'FileVault settings opened', detail: result.ok ? 'Enable disk encryption only when you have recovery key handling in place. Rescan after the OS confirms protection is enabled.' : result.error, command: result.command };
  }

  if (action === 'open-antivirus-settings') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process windowsdefender:'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension']);
    return { ok: result.ok, action, title: process.platform === 'win32' ? 'Defender settings opened' : 'Security settings opened', detail: result.ok ? 'Enable real-time protection or confirm the approved endpoint security tool is active, then rescan.' : result.error, command: result.command };
  }

  if (action === 'cleanup-caches') {
    const targets = oncallSupportDataTargets().filter((target) => /oncalltechsa/i.test(target));
    const uniqueTargets = [...new Set(targets)];
    const results = [];
    for (const target of uniqueTargets) {
      results.push(await cleanDirectoryContents(target));
    }
    const freedGb = Math.round(results.reduce((sum, item) => sum + item.freedGb, 0) * 10) / 10;
    return {
      ok: true,
      action,
      title: 'OnCallTechSA support cache cleanup complete',
      detail: `Cleared only OnCallTechSA support-app cache/session folders. No user documents, browser data, mail, photos, or third-party app caches were touched. Estimated freed space: ${freedGb}GB.`,
      results
    };
  }

  if (action === 'deactivate-once-off-session') {
    const targets = oncallSupportDataTargets().filter((target) => /oncalltechsa/i.test(target));
    const results = [];
    for (const target of [...new Set(targets)]) {
      results.push(await cleanDirectoryContents(target));
    }
    return {
      ok: true,
      action,
      title: 'Once-off support session deactivated',
      detail: 'Local OnCallTechSA support session data was cleared. Use the operating system uninstall flow to remove the app package; the scanner will not remove user files or silently delete itself.',
      uninstallInstructions: process.platform === 'win32'
        ? ['Open Windows Settings > Apps > Installed apps.', 'Search for OnCallTechSA Support App.', 'Choose Uninstall and approve the Windows prompt.']
        : ['Quit OnCallTechSA Support App.', 'Open Applications.', 'Move OnCallTechSA Support App to Trash.', 'Empty Trash only if you are sure you no longer need the app.'],
      results
    };
  }

  if (action === 'open-storage-settings') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process ms-settings:storagesense'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.Storage']);
    return { ok: result.ok, action, title: 'Storage settings opened', detail: result.ok ? 'Review large files, downloads, applications, and local cloud sync storage.' : result.error, command: result.command };
  }

  if (action === 'open-software-update') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process ms-settings:windowsupdate'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.Software-Update-Settings.extension']);
    return { ok: result.ok, action, title: 'Software update opened', detail: result.ok ? 'Use the operating system update screen to install updates. Admin approval or restart may be required.' : result.error, command: result.command };
  }

  if (action === 'open-security-settings') {
    const result = process.platform === 'win32'
      ? await runCommand('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Start-Process windowsdefender:'])
      : await runCommand('/usr/bin/open', ['x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension']);
    return { ok: result.ok, action, title: 'Security settings opened', detail: result.ok ? 'Use the operating system security screen for FileVault, firewall, Defender, and privacy controls. Admin approval may be required.' : result.error, command: result.command };
  }

  if (action === 'flush-dns') {
    const result = process.platform === 'win32'
      ? await runCommand('ipconfig.exe', ['/flushdns'])
      : await runCommand('/usr/bin/dscacheutil', ['-flushcache']);
    return { ok: result.ok, action, title: 'DNS cache flushed', detail: result.ok ? 'DNS resolver cache was refreshed. Re-test connectivity if a website or email host was failing.' : result.error, command: result.command, output: result.output };
  }

  return { ok: false, action, error: 'Unsupported repair action.' };
}

let activeServer = null;
let startPromise = null;
let intentionalClose = false;

function createLocalScanServer() {
  return http.createServer(async (req, res) => {
  const origin = req.headers.origin || '';
  const requestUrl = new URL(req.url || '/', `http://${host}:${port}`);

  if (!isLoopbackRequest(req)) {
    return sendJson(res, 403, { ok: false, error: 'Local scanner only accepts loopback requests.' }, origin);
  }

  if (req.method === 'OPTIONS') {
    return sendPreflight(res, origin);
  }

  if (!isAllowedOrigin(origin)) {
    return sendJson(res, 403, { ok: false, error: 'Origin is not allowed. Use the deployed OnCallTechSA support app or installed desktop app.' }, origin);
  }

  if (req.method === 'GET' && requestUrl.pathname === '/health') {
    return sendJson(res, 200, {
      ok: true,
      service: 'OnCallTechSA local scanner',
      platform: process.platform,
      sessionToken: scannerSessionToken,
      policy: {
        zeroTrust: true,
        loopbackOnly: true,
        fixedPort: port,
        allowedOrigin: origin,
        requiresSessionTokenForPost: true,
        maxListeningPorts: 1
      },
      privacy: 'Read-only scan service. No private files, emails, browser history, passwords, messages, or banking details are read.'
    }, origin);
  }

  if (req.method === 'POST' && requestUrl.pathname === '/scan') {
    try {
      if (!hasScannerToken(req)) {
        return sendJson(res, 401, { ok: false, error: 'Scanner session token is required.' }, origin);
      }
      const body = await readBody(req);
      if (body.consent !== true) {
        return sendJson(res, 400, { ok: false, error: 'Customer consent is required before scanning.' }, origin);
      }

      const scan = await runHealthScan();
      return sendJson(res, 200, scan, origin);
    } catch (error) {
      return sendJson(res, 500, { ok: false, error: error.message || 'Scan failed.' }, origin);
    }
  }

  if (req.method === 'POST' && requestUrl.pathname === '/repair') {
    try {
      if (!hasScannerToken(req)) {
        return sendJson(res, 401, { ok: false, error: 'Scanner session token is required.' }, origin);
      }
      const body = await readBody(req);
      if (body.consent !== true || body.ownerTest !== true) {
        return sendJson(res, 403, { ok: false, error: 'Internal owner consent is required before repair actions.' }, origin);
      }

      const result = await repairAction(String(body.action || ''));
      return sendJson(res, result.ok ? 200 : 400, result, origin);
    } catch (error) {
      return sendJson(res, 500, { ok: false, error: error.message || 'Repair action failed.' }, origin);
    }
  }

  return sendJson(res, 404, { ok: false, error: 'Not found.' }, origin);
  });
}

function startLocalScanServer({ log = console.log } = {}) {
  if (activeServer?.listening) {
    return Promise.resolve({ ok: true, host, port, alreadyRunning: true });
  }
  if (startPromise) return startPromise;

  activeServer = createLocalScanServer();
  intentionalClose = false;
  startPromise = new Promise((resolve) => {
    activeServer.once('error', (error) => {
      const portInUse = error?.code === 'EADDRINUSE';
      if (!portInUse) {
        log?.(`OnCallTechSA local scanner failed to start: ${error.message}`);
      }
      resolve({
        ok: false,
        host,
        port,
        alreadyRunning: portInUse,
        error: portInUse ? 'Local scanner port is already in use.' : error.message
      });
    });
    activeServer.listen(port, host, () => {
      log?.(`OnCallTechSA local scanner listening on http://${host}:${port}`);
      resolve({ ok: true, host, port, alreadyRunning: false });
    });
    activeServer.on('close', () => {
      if (!intentionalClose) log?.('OnCallTechSA local scanner stopped unexpectedly.');
    });
  }).finally(() => {
    startPromise = null;
  });
  return startPromise;
}

function stopLocalScanServer() {
  return new Promise((resolve) => {
    if (!activeServer) return resolve({ ok: true, stopped: false });
    const serverToClose = activeServer;
    activeServer = null;
    intentionalClose = true;
    serverToClose.close((error) => {
      resolve({ ok: !error, stopped: !error, error: error?.message || '' });
    });
  });
}

if (require.main === module) {
  startLocalScanServer().then((result) => {
    if (result.ok) {
      console.log('Keep this terminal open while running real scans from the web app.');
    } else if (result.alreadyRunning) {
      console.log('OnCallTechSA local scanner is already running on this device.');
    }
  });
}

module.exports = {
  startLocalScanServer,
  stopLocalScanServer,
  port,
  host
};
