/**
 * Function/Tools/file-info.js
 * ---------------------------------------------------------------------------
 * FILE INFORMATION
 *
 * Everything about one file: name, size, extension, timestamps, attributes,
 * signature/version details for executables, and its SHA-256. Read-only.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

/** Recognise the real type from the first bytes, not only the extension. */
const SIGNATURES = [
  { magic: '4d5a', type: 'Windows executable / DLL (MZ)' },
  { magic: '504b0304', type: 'ZIP archive (or Office/OpenDocument file)' },
  { magic: '52617221', type: 'RAR archive' },
  { magic: '377abcaf271c', type: '7-Zip archive' },
  { magic: '25504446', type: 'PDF document' },
  { magic: '89504e47', type: 'PNG image' },
  { magic: 'ffd8ff', type: 'JPEG image' },
  { magic: '47494638', type: 'GIF image' },
  { magic: '1f8b', type: 'GZIP archive' },
  { magic: '424d', type: 'BMP image' },
  { magic: '000001ba', type: 'MPEG video' },
  { magic: '52494646', type: 'RIFF container (AVI / WAV)' },
  { magic: '664c6143', type: 'FLAC audio' },
  { magic: '494433', type: 'MP3 audio (ID3)' },
  { magic: '7f454c46', type: 'ELF binary' },
  { magic: 'd0cf11e0', type: 'Legacy Office document' },
];

function detectType(file) {
  try {
    const handle = fs.openSync(file, 'r');
    const buffer = Buffer.alloc(16);
    const read = fs.readSync(handle, buffer, 0, 16, 0);
    fs.closeSync(handle);
    const head = buffer.slice(0, read).toString('hex');
    const match = SIGNATURES.find((entry) => head.startsWith(entry.magic));
    return match ? match.type : null;
  } catch {
    return null;
  }
}

function hashFile(file) {
  return new Promise((resolve) => {
    const hash = crypto.createHash('sha256');
    const stream = fs.createReadStream(file);
    stream.on('data', (chunk) => hash.update(chunk));
    stream.on('end', () => resolve(hash.digest('hex')));
    stream.on('error', () => resolve(null));
  });
}

/** Version resource and Authenticode signature - executables only. */
async function fileDetails(file) {
  const script = [
    '$ErrorActionPreference = "SilentlyContinue"',
    'function SafeGet([scriptblock] $block) { try { & $block } catch { $null } }',
    `$path = "${String(file).replace(/"/g, '`"')}"`,
    '$item = SafeGet { Get-Item -LiteralPath $path -ErrorAction Stop | Select-Object Attributes, LinkType }',
    '$version = SafeGet { (Get-Item -LiteralPath $path -ErrorAction Stop).VersionInfo | Select-Object FileVersion, ProductVersion, CompanyName, FileDescription, ProductName, LegalCopyright, OriginalFilename }',
    '$signature = SafeGet { Get-AuthenticodeSignature -LiteralPath $path -ErrorAction Stop | Select-Object Status, @{n="Signer";e={ if ($_.SignerCertificate) { $_.SignerCertificate.Subject } else { $null } }} }',
    '$owner = SafeGet { (Get-Acl -LiteralPath $path -ErrorAction Stop).Owner }',
    '[ordered]@{ item = $item; version = $version; signature = $signature; owner = $owner } | ConvertTo-Json -Depth 4 -Compress',
  ].join('\n');

  const result = await system.runPowerShellJson(script, { timeoutMs: 45000 });
  return result.ok && result.data ? result.data : {};
}

module.exports = {
  name: 'File Information',
  menuName: 'File Information',
  description: 'Name, size, type, timestamps, attributes, version, signature and SHA-256 of a file.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Tools',
  order: 20,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['filesystem:read', 'filesystem:write(reports)'],
  tags: ['file', 'info', 'properties', 'hash', 'signature', 'version'],
  documentation:
    'Inspects a single file: full path, size, extension, created/modified/accessed timestamps, ' +
    'attributes and owner, the real file type detected from its magic bytes (so a renamed .exe is ' +
    'still recognised), the version resource and Authenticode signature for executables, and the ' +
    'SHA-256 checksum. Everything is read-only.',

  async selfTest() {
    const type = detectType(__filename);
    return { ok: true, message: type ? `type detection works (${type})` : 'ready' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('File Information', ['Everything Windows knows about one file.']);

    for (;;) {
      const input = (await prompt.text('File path (empty to go back)')).replace(/^"|"$/g, '').trim();
      if (!input) break;

      let stat;
      try {
        stat = fs.statSync(input);
      } catch (err) {
        ui.blank();
        ui.error(`The file could not be read: ${err.code === 'ENOENT' ? 'it does not exist' : err.message}`);
        continue;
      }
      if (stat.isDirectory()) {
        ui.blank();
        ui.error('That is a folder - use the Folder Size Analyzer for directories.');
        continue;
      }

      const full = path.resolve(input);
      const spin = ui.spinner('Reading file information...');
      const [hash, details] = await Promise.all([hashFile(full), fileDetails(full)]);
      spin.stop();

      const version = details.version || {};
      const signature = details.signature || {};
      const detected = detectType(full);

      ui.blank();
      ui.section('FILE');
      ui.blank();
      kit.kv('File Name', path.basename(full));
      kit.kv('Extension', path.extname(full) || '(none)');
      kit.kv('File Size', `${system.formatBytes(stat.size)}  (${system.formatNumber(stat.size)} bytes)`);
      kit.kv('Full Path', full);
      kit.kv('Detected Type', detected || 'unrecognised signature');
      kit.kv('Attributes', system.clean(details.item && details.item.Attributes));
      kit.kv('Owner', system.clean(details.owner));

      ui.blank();
      ui.section('TIMESTAMPS');
      ui.blank();
      kit.kv('Created Date', system.isoLocal(stat.birthtime));
      kit.kv('Modified Date', system.isoLocal(stat.mtime));
      kit.kv('Last Accessed', system.isoLocal(stat.atime));

      if (system.clean(version.FileVersion) || system.clean(version.CompanyName)) {
        ui.blank();
        ui.section('VERSION INFORMATION');
        ui.blank();
        kit.kv('File Description', system.clean(version.FileDescription));
        kit.kv('Product Name', system.clean(version.ProductName));
        kit.kv('File Version', system.clean(version.FileVersion));
        kit.kv('Product Version', system.clean(version.ProductVersion));
        kit.kv('Company', system.clean(version.CompanyName));
        kit.kv('Original File Name', system.clean(version.OriginalFilename));
        kit.kv('Copyright', system.clean(version.LegalCopyright));
      }

      if (system.clean(signature.Status)) {
        ui.blank();
        ui.section('DIGITAL SIGNATURE');
        ui.blank();
        const valid = String(signature.Status) === 'Valid';
        kit.kv('Signature Status', system.clean(signature.Status), valid ? 'brightGreen' : 'brightYellow');
        const signer = system.clean(signature.Signer);
        if (signer) {
          const common = signer.match(/CN=([^,]+)/);
          kit.kv('Signed By', common ? common[1] : signer);
        }
      }

      ui.blank();
      ui.section('CHECKSUM');
      ui.blank();
      ui.println('  ' + ui.c.gray('SHA256'));
      ui.println('  ' + ui.c.brightGreen(hash || 'not available'));

      await kit.offerExport('File Information Report', 'FileInfo', (report) => {
        const section = report.section('FILE');
        section.add('File Name', path.basename(full));
        section.add('Full Path', full);
        section.add('Extension', path.extname(full) || '(none)');
        section.add('File Size', `${system.formatBytes(stat.size)} (${system.formatNumber(stat.size)} bytes)`);
        section.add('Detected Type', detected);
        section.add('Owner', system.clean(details.owner));
        section.add('Attributes', system.clean(details.item && details.item.Attributes));

        const times = report.section('TIMESTAMPS');
        times.add('Created Date', system.isoLocal(stat.birthtime));
        times.add('Modified Date', system.isoLocal(stat.mtime));
        times.add('Last Accessed', system.isoLocal(stat.atime));

        const versionSection = report.section('VERSION INFORMATION');
        versionSection.add('File Description', system.clean(version.FileDescription));
        versionSection.add('Product Name', system.clean(version.ProductName));
        versionSection.add('File Version', system.clean(version.FileVersion));
        versionSection.add('Company', system.clean(version.CompanyName));
        versionSection.add('Copyright', system.clean(version.LegalCopyright));

        const signatureSection = report.section('DIGITAL SIGNATURE');
        signatureSection.add('Status', system.clean(signature.Status));
        signatureSection.add('Signer', system.clean(signature.Signer));

        const checksum = report.section('CHECKSUM');
        checksum.add('SHA256', hash);
      });

      ui.blank();
      const again = await prompt.confirm('Inspect another file?');
      if (again !== true) break;
    }

    return kit.done('File information closed.');
  },
};
