/**
 * Function/Reports/log-viewer.js
 * ---------------------------------------------------------------------------
 * LOG VIEWER
 *
 * Browse the application logs with filters by level, module and date, a text
 * search, an errors-only view and export of the current selection.
 * ---------------------------------------------------------------------------
 */

'use strict';

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

/** [2026-08-24 19:56:31] [INFO ] [module          ] message | extra */
const LINE_PATTERN = /^\[([\d-]+ [\d:]+)\]\s+\[(\w+)\s*\]\s+\[([^\]]+)\]\s+(.*)$/;

function parseLine(line) {
  const match = String(line).match(LINE_PATTERN);
  if (!match) return null;
  return {
    time: match[1],
    level: match[2].toUpperCase(),
    scope: match[3].trim(),
    message: match[4],
    raw: line,
  };
}

function readLog(file) {
  try {
    return fs
      .readFileSync(file, 'utf8')
      .split(/\r?\n/)
      .filter((line) => line.trim())
      .map(parseLine)
      .filter(Boolean);
  } catch {
    return [];
  }
}

function levelColor(level) {
  if (level === 'ERROR') return 'brightRed';
  if (level === 'WARN') return 'yellow';
  if (level === 'DEBUG') return 'gray';
  return 'white';
}

function renderEntries(ui, entries, limit = 40) {
  if (!entries.length) {
    ui.println('  ' + ui.c.gray('No entry matches.'));
    return;
  }
  for (const entry of entries.slice(-limit)) {
    ui.println(
      '  ' + ui.c.gray(entry.time.slice(5)) + ' ' +
        ui.c[levelColor(entry.level)](ui.padEnd(entry.level, 6)) +
        ui.c.brightCyan(ui.padEnd(ui.truncate(entry.scope, 16), 17)) +
        ui.c.white(ui.truncate(entry.message, ui.getWidth() - 34))
    );
  }
  if (entries.length > limit) {
    ui.blank();
    ui.println('  ' + ui.c.gray(`(showing the last ${limit} of ${entries.length} entries)`));
  }
}

module.exports = {
  name: 'Log Viewer',
  menuName: 'Log Viewer',
  description: 'Browse application logs with filters by level, module, date and text search.',
  version: '1.0.0',
  author: 'InfinitySniper HFX',
  category: 'Reports',
  order: 60,
  requiredPermissions: 'normal',
  destructive: false,
  dependencies: [],
  minimumAppVersion: '1.0.0',
  permissions: ['filesystem:read(logs)', 'filesystem:write(reports)'],
  tags: ['logs', 'errors', 'debug', 'history', 'search'],
  documentation:
    'Reads the daily application logs and lets you filter them by level (error, warning, info, ' +
    'debug), by the module that produced them, or by a text search, with a dedicated errors-only ' +
    'view and a list of the recorded Error IDs. The current selection can be exported as a TXT ' +
    'report - useful when asking for help about a specific failure.',

  async selfTest() {
    const logs = logger.listLogs();
    return logs.length
      ? { ok: true, message: `${logs.length} log file(s)` }
      : { ok: false, message: 'no log file found' };
  },

  async execute(ctx) {
    const { ui, prompt } = ctx;
    kit.header('Log Viewer', ['Application logs with filters and search.']);

    const logs = logger.listLogs().filter((entry) => /^app-/.test(entry.name));
    if (!logs.length) {
      ui.blank();
      ui.info('No application log has been written yet.');
      await prompt.pause('Press ENTER to go back');
      return { ok: true, logs: 0 };
    }

    let file = logs[0];
    let entries = readLog(file.path);
    let view = entries;
    let title = 'ALL ENTRIES';

    for (;;) {
      const levels = entries.reduce((acc, entry) => {
        acc[entry.level] = (acc[entry.level] || 0) + 1;
        return acc;
      }, {});

      ui.blank();
      ui.section('LOG FILE');
      ui.blank();
      kit.kv('File', file.name);
      kit.kv('Size', system.formatBytes(file.size));
      kit.kv('Entries', system.formatNumber(entries.length));
      kit.kv('Errors', system.formatNumber(levels.ERROR || 0), levels.ERROR ? 'brightRed' : 'brightGreen');
      kit.kv('Warnings', system.formatNumber(levels.WARN || 0), levels.WARN ? 'brightYellow' : 'brightGreen');
      kit.kv('Recorded Error IDs', system.formatNumber(errors.list(500).length));

      ui.blank();
      ui.section(title);
      ui.blank();
      renderEntries(ui, view);

      ui.blank();
      ui.menu(
        [
          { key: '1', label: 'Choose another log file (by date)' },
          { key: '2', label: 'Filter by level' },
          { key: '3', label: 'Filter by module' },
          { key: '4', label: 'Search the log' },
          { key: '5', label: 'Show errors only' },
          { key: '6', label: 'Show recorded Error IDs' },
          { key: '7', label: 'Export the current view' },
          { key: '8', label: 'Show all entries again' },
          { separator: true },
          { key: '0', label: 'Back', color: 'gray' },
        ],
        { title: 'LOG VIEWER' }
      );
      ui.blank();

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

      switch (answer) {
        case '1': {
          const index = await prompt.choose(
            'Which log file?',
            logs.map((entry) => ({ label: entry.name, hint: `${system.formatBytes(entry.size)} - ${system.isoLocal(entry.modified).slice(0, 10)}` }))
          );
          if (index === null) break;
          file = logs[index];
          entries = readLog(file.path);
          view = entries;
          title = 'ALL ENTRIES';
          break;
        }

        case '2': {
          const available = ['ERROR', 'WARN', 'INFO', 'DEBUG'];
          const index = await prompt.choose('Which level?', available);
          if (index === null) break;
          view = entries.filter((entry) => entry.level === available[index]);
          title = `LEVEL: ${available[index]} (${view.length})`;
          break;
        }

        case '3': {
          const scopes = [...new Set(entries.map((entry) => entry.scope))].sort();
          const index = await prompt.choose('Which module?', scopes);
          if (index === null) break;
          view = entries.filter((entry) => entry.scope === scopes[index]);
          title = `MODULE: ${scopes[index].toUpperCase()} (${view.length})`;
          break;
        }

        case '4': {
          const term = (await prompt.text('Search for')).trim().toLowerCase();
          if (!term) break;
          view = entries.filter((entry) => entry.raw.toLowerCase().includes(term));
          title = `SEARCH: ${term.toUpperCase()} (${view.length})`;
          break;
        }

        case '5':
          view = entries.filter((entry) => entry.level === 'ERROR' || entry.level === 'WARN');
          title = `ERRORS AND WARNINGS (${view.length})`;
          break;

        case '6': {
          const recorded = errors.list(30);
          ui.blank();
          ui.section('RECORDED ERROR IDs');
          ui.blank();
          if (!recorded.length) {
            ui.println('  ' + ui.c.brightGreen('No error has been recorded.'));
          } else {
            kit.table(
              ['ERROR ID', 'MODULE', 'TIME', 'MESSAGE'],
              recorded.map((entry) => [
                ui.c.brightYellow(entry.id),
                ui.c.brightWhite(ui.truncate(entry.module, 16)),
                ui.c.gray(entry.time.slice(5, 16)),
                ui.c.gray(ui.truncate(entry.message, 26)),
              ]),
              { limit: 20 }
            );
          }
          await prompt.pause();
          break;
        }

        case '7': {
          const report = kit.report('Log Report');
          const section = report.section('LOG');
          section.add('Log File', file.name);
          section.add('View', title);
          section.add('Entries In View', system.formatNumber(view.length));
          section.add('Total Entries', system.formatNumber(entries.length));

          const list = report.section('ENTRIES');
          for (const entry of view) list.note(entry.raw);

          const recorded = errors.list(100);
          if (recorded.length) {
            const errorSection = report.section('RECORDED ERROR IDS');
            for (const entry of recorded) {
              const group = errorSection.group(entry.id);
              group.add('Module', entry.module);
              group.add('Time', entry.time);
              group.add('Message', entry.message);
              group.add('Context', entry.context);
              group.add('Resolved', entry.resolved ? 'Yes' : 'No');
            }
          }
          kit.saveReport(report, 'LogReport');
          await prompt.pause();
          break;
        }

        case '8':
          view = entries;
          title = 'ALL ENTRIES';
          break;

        default:
          ui.warning('Enter one of the numbers shown above, or 0 to go back.');
          await prompt.pause();
      }
    }

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