/**
 * Function/Windows/time-check.js
 * ---------------------------------------------------------------------------
 * SYSTEM TIME CHECK
 *
 * Local time, time zone, the Windows Time service and the configured NTP
 * source. Synchronisation is offered as an explicit, confirmed action that
 * uses the standard Windows mechanism (w32tm).
 * ---------------------------------------------------------------------------
 */

'use strict';

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

const QUERY = [
  '$ErrorActionPreference = "SilentlyContinue"',
  'function SafeGet([scriptblock] $block) { try { & $block } catch { $null } }',
  '$now = Get-Date',
  '$tz = SafeGet { Get-TimeZone -ErrorAction Stop | Select-Object Id, DisplayName, StandardName, BaseUtcOffset, SupportsDaylightSaving }',
  '$service = SafeGet { Get-CimInstance Win32_Service -Filter "Name = \'W32Time\'" -ErrorAction Stop | Select-Object Name, DisplayName, State, StartMode }',
  '$source = SafeGet { (w32tm /query /source) -join "" }',
  '$status = SafeGet { (w32tm /query /status) -join "`n" }',
  '$config = SafeGet { (w32tm /query /configuration) -join "`n" }',
  '[ordered]@{',
  '  localTime = $now.ToString("yyyy-MM-dd HH:mm:ss");',
  '  utcTime = $now.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss");',
  '  dayOfWeek = $now.DayOfWeek.ToString();',
  '  timeZone = $tz;',
  '  service = $service;',
  '  source = $source;',
  '  status = $status;',
  '  config = $config',
  '} | ConvertTo-Json -Depth 5 -Compress',
].join('\n');

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

/** Pull a labelled value out of the w32tm /query /status output. */
function statusValue(status, label) {
  const line = String(status || '')
    .split('\n')
    .find((entry) => entry.toLowerCase().includes(String(label).toLowerCase()));
  if (!line) return null;
  const parts = line.split(':');
  return parts.length > 1 ? parts.slice(1).join(':').trim() : null;
}

/** Offset between this machine and a public time source, via HTTP Date. */
async function measureDrift() {
  const https = require('https');
  return new Promise((resolve) => {
    const started = Date.now();
    const request = https.request(
      { host: 'www.google.com', method: 'HEAD', path: '/', timeout: 8000 },
      (response) => {
        const roundTrip = Date.now() - started;
        const header = response.headers.date;
        response.resume();
        if (!header) return resolve(null);
        const remote = new Date(header).getTime();
        // Compensate roughly for half the round trip.
        const local = Date.now() - roundTrip / 2;
        return resolve({ driftMs: local - remote, roundTrip, remote: new Date(remote) });
      }
    );
    request.on('timeout', () => {
      request.destroy();
      resolve(null);
    });
    request.on('error', () => resolve(null));
    request.end();
  });
}

module.exports = {
  name: 'System Time Check',
  menuName: 'System Time Check',
  description: 'Local time, time zone, Windows Time service, NTP source and optional resynchronisation.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Windows',
  order: 100,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['system:read', 'time:sync(confirmed, admin)'],
  tags: ['time', 'clock', 'ntp', 'timezone', 'sync'],
  documentation:
    'Shows the current local and UTC time, the configured time zone with its UTC offset and DST ' +
    'support, the state of the Windows Time service, the NTP source in use and the last successful ' +
    'synchronisation. It can also compare the clock against a public HTTPS time source to estimate ' +
    'drift. Resynchronising uses the standard "w32tm /resync" command and requires administrator ' +
    'rights and a confirmation - the clock is never changed silently.',

  async selfTest(ctx) {
    if (!ctx.system.IS_WINDOWS) return { ok: false, message: 'Windows only' };
    const data = await collect();
    return data ? { ok: true, message: 'time information available' } : { ok: false, message: 'time query failed' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('System Time Check', ['Clock, time zone and time synchronisation.']);
    if (!kit.requireWindows('The time check')) return { ok: false, reason: 'unsupported-platform' };

    const spin = ui.spinner('Reading time configuration...');
    const data = await collect();
    spin.stop();
    if (!data) return kit.noData('time information');

    const zone = data.timeZone || {};
    const service = data.service || {};

    ui.section('CURRENT TIME');
    ui.blank();
    kit.kv('Local Time', system.clean(data.localTime), 'brightGreen');
    kit.kv('UTC Time', system.clean(data.utcTime));
    kit.kv('System Date', String(data.localTime || '').slice(0, 10));
    kit.kv('Day Of Week', system.clean(data.dayOfWeek));

    ui.blank();
    ui.section('TIME ZONE');
    ui.blank();
    kit.kv('Time Zone', system.clean(zone.DisplayName) || system.clean(zone.StandardName));
    kit.kv('Time Zone ID', system.clean(zone.Id));
    kit.kv('Daylight Saving', zone.SupportsDaylightSaving === undefined ? null : zone.SupportsDaylightSaving ? 'Supported' : 'Not used');

    ui.blank();
    ui.section('TIME SERVICE');
    ui.blank();
    kit.kv(
      'Windows Time Service',
      service.State ? `${service.State} (${service.StartMode})` : null,
      String(service.State) === 'Running' ? 'brightGreen' : 'brightYellow'
    );
    kit.kv('Time Source', system.clean(data.source));
    kit.kv('Last Successful Sync', statusValue(data.status, 'Last Successful Sync Time'));
    kit.kv('Stratum', statusValue(data.status, 'Stratum'));
    kit.kv('Poll Interval', statusValue(data.status, 'Poll Interval'));

    for (;;) {
      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Compare the clock with an internet time source' },
          { key: '2', label: 'Synchronise time now (advanced)', color: 'brightYellow' },
          { key: '3', label: 'Export as a TXT report' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'SYSTEM TIME' }
      );
      ui.blank();

      const answer = await prompt.select();
      if (!answer && prompt.isClosed()) break;
      if (answer === '0') break;

      if (answer === '1') {
        const spin2 = ui.spinner('Comparing with an internet time source...');
        const drift = await measureDrift();
        spin2.stop();

        ui.blank();
        if (!drift) {
          ui.warning('The internet time source could not be reached - is this machine online?');
        } else {
          const seconds = drift.driftMs / 1000;
          kit.kv('Reference Time', system.isoLocal(drift.remote));
          kit.kv('Clock Difference', `${seconds >= 0 ? '+' : ''}${seconds.toFixed(1)} s`, Math.abs(seconds) > 60 ? 'brightRed' : Math.abs(seconds) > 5 ? 'brightYellow' : 'brightGreen');
          kit.kv('Measurement Accuracy', `+/- ${(drift.roundTrip / 2000).toFixed(1)} s (network round trip)`);
          ui.blank();
          if (Math.abs(seconds) > 60) ui.error('The clock is off by more than a minute - synchronise it.');
          else if (Math.abs(seconds) > 5) ui.warning('The clock drifts by a few seconds.');
          else ui.completed('The clock is accurate.');
        }
        await prompt.pause();
      } else if (answer === '2') {
        if (ctx.safeMode) {
          ui.blank();
          ui.warning('Safe Mode is enabled - time synchronisation is blocked.');
          await prompt.pause();
          continue;
        }
        if (ctx.dryRun) {
          ui.blank();
          ui.info('Dry Run: the clock would be resynchronised with "w32tm /resync". Nothing was changed.');
          await prompt.pause();
          continue;
        }
        if (!ctx.isAdmin) {
          ui.blank();
          ui.warning('Time synchronisation requires administrator rights.');
          ui.println('  ' + ui.c.gray('Restart the application as administrator and try again.'));
          await prompt.pause();
          continue;
        }

        ui.blank();
        ui.println('  ' + ui.c.gray('This runs the standard Windows command: w32tm /resync'));
        const confirmed = await prompt.confirm('Synchronise the system clock now?');
        if (confirmed !== true) continue;

        const spin3 = ui.spinner('Synchronising with the time server...');
        const outcome = await system.runPowerShell(
          '$ErrorActionPreference = "Continue"\nStart-Service W32Time -ErrorAction SilentlyContinue\n(w32tm /resync /force) -join "`n"',
          { timeoutMs: 90000 }
        );
        spin3.stop();

        ui.blank();
        const text = outcome.stdout.trim();
        if (/successfully/i.test(text)) {
          ui.completed('The clock was synchronised.');
          ctx.logger.info('time-check', 'System time resynchronised');
        } else {
          ui.warning('Windows did not confirm the synchronisation.');
          for (const line of text.split('\n').slice(0, 4)) {
            if (line.trim()) ui.println('  ' + ui.c.gray(ui.truncate(line.trim(), ui.getWidth())));
          }
        }
        await prompt.pause();
      } else if (answer === '3') {
        const report = kit.report('System Time Report');
        const section = report.section('SYSTEM TIME');
        section.add('Local Time', system.clean(data.localTime));
        section.add('UTC Time', system.clean(data.utcTime));
        section.add('Time Zone', system.clean(zone.DisplayName));
        section.add('Time Zone ID', system.clean(zone.Id));
        section.add('Windows Time Service', service.State ? `${service.State} (${service.StartMode})` : null);
        section.add('Time Source', system.clean(data.source));
        section.add('Last Successful Sync', statusValue(data.status, 'Last Successful Sync Time'));
        const status = report.section('W32TM STATUS');
        String(data.status || '')
          .split('\n')
          .forEach((line) => {
            if (line.trim()) status.note(line.trim());
          });
        kit.saveReport(report, 'SystemTime');
        await prompt.pause();
      } else {
        ui.warning('Enter one of the numbers shown above, or 0 to go back.');
        await prompt.pause();
      }
    }

    return kit.done('Time check finished.');
  },
};
