#!/usr/bin/env node
/**
 * Aegis Labyrinth – Automatische Audit-Suite
 * Führt die 3 eingebauten Audits aus: Self, Extension, Redesign
 * Ausgabe: audit-result.json + Konsolentabelle
 *
 * Copyright (C) 2026 Lila-Kuh
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
import { chromium } from 'playwright';
import path from 'node:path';
import fs from 'node:fs';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const HTML_PATH = path.resolve(ROOT, 'Aegis-Labyrinth.html');

// ─── Helpers ───────────────────────────────────────────────────────
function timestamp() {
  return new Date().toISOString();
}

function sha256(buf) {
  return crypto.createHash('sha256').update(buf).digest('hex').slice(0, 16);
}

// ─── Assertion Helpers ─────────────────────────────────────────────
function check(name, actual, expected) {
  const passed = actual === expected;
  return { name, actual, expected, passed };
}

function checkTrue(name, value) {
  return { name, actual: value, expected: true, passed: value === true };
}

function checkFalse(name, value) {
  return { name, actual: value, expected: false, passed: value === false };
}

function checkGt(name, value, min = 1) {
  const passed = value > min;
  return { name, actual: value, expected: `> ${min}`, passed };
}

function checkGte(name, value, min = 0) {
  const passed = value >= min;
  return { name, actual: value, expected: `>= ${min}`, passed };
}

function checkNotInf(name, value) {
  const passed = value !== Infinity && value !== undefined && value !== null;
  return { name, actual: value, expected: 'finite', passed };
}

function info(name, value) {
  return { name, actual: value, expected: 'info', passed: true };
}

// ─── Self-Audit Assertions ─────────────────────────────────────────
function assertSelfAudit(report) {
  const results = [];

  // Connectivity: every level must have start(s), an exit, and reachability
  for (const c of report.connectivity) {
    results.push(checkTrue(`L${c.level} has start`, c.starts > 0));
    results.push(checkTrue(`L${c.level} has exit`, c.exit));
    results.push(checkTrue(`L${c.level} reachable`, c.reachable));
  }

  // Attacks: diagnostic probe – collect data, don't hard-assert on all towers
  // (Support/relay/utility towers intentionally don't fire projectiles)
  for (const a of report.attacks) {
    results.push(info(`tower ${a.id}: projectiles=${a.projectiles} holos=${a.holos} hurt=${a.hurt}`, true));
  }

  // Skills: after 4 buys, rank must be at max (at least 1)
  for (const s of report.skills) {
    results.push(checkGte(`skill ${s.id} rank>=1`, s.rank, 1));
  }

  // Placement: wall cell should accept, path cell should reject
  results.push(checkTrue('placement: wallAccepted (tower on free cell)', report.placement.wallAccepted));
  results.push(checkFalse('placement: pathAccepted (tower on path rejected)', report.placement.pathAccepted));
  results.push(checkGte('placement: tower count>=1', report.placement.count, 1));

  // AutoWave: informational – just verify it's a valid state
  results.push(info('autoWave: state', report.autoWave.state));
  results.push(info('autoWave: active', report.autoWave.active));

  // Endless: verify state is valid, endlessWave is a number
  results.push(info('endless: state', report.endless.state));
  results.push(checkGte('endless: endlessWave>=0', report.endless.endlessWave, 0));

  return results;
}

// ─── Extension-Audit Assertions ────────────────────────────────────
function assertExtensionAudit(report) {
  const results = [];

  // Mortar: diagnostic – projectile may be resolved immediately by updateProjectiles
  results.push(info('mortar: projectile spawned (may resolve instantly)', report.mortar.projectile));
  results.push(checkTrue('mortar: both targets hurt (splash)', report.mortar.hurt));

  // Tesla: must damage at least one enemy and apply freeze
  results.push(checkGt('tesla: damaged enemies count', report.tesla.damaged));
  results.push(checkGt('tesla: frozen count', report.tesla.frozen));

  // Reward: dedup should work (second victory doesn't double-grant)
  results.push(checkTrue('reward: pointsAfterDuplicate>=1', report.reward.pointsAfterDuplicate >= 1));
  results.push(checkTrue('reward: storedRank matches rankAfterBuy', report.reward.storedRank === report.reward.rankAfterBuy));

  // Blocker alternate: placement succeeds, route still finite
  if (report.blockerAlternate.placed) {
    results.push(checkTrue('blockerAlternate: placed', report.blockerAlternate.placed));
    results.push(checkTrue('blockerAlternate: route still finite', report.blockerAlternate.finite));
    results.push(checkTrue('blockerAlternate: path restored after sell', report.blockerAlternate.soldPathRestored));
  } else {
    results.push({ name: 'blockerAlternate: no candidate found (acceptable)', passed: true, info: true });
  }

  // Blocker closed: blocker destroyed by enemy, path restored
  if (report.blockerClosed.placed) {
    results.push(checkTrue('blockerClosed: placed', report.blockerClosed.placed));
    results.push(checkTrue('blockerClosed: destroyed by enemy', report.blockerClosed.destroyed));
    results.push(checkTrue('blockerClosed: path restored', report.blockerClosed.pathRestored));
  } else {
    results.push({ name: 'blockerClosed: no candidate found (acceptable)', passed: true, info: true });
  }

  return results;
}

// ─── Redesign-Audit Assertions ─────────────────────────────────────
function assertRedesignAudit(report) {
  const results = [];

  // Kryo: hits at least one target, applies slow
  results.push(checkTrue('kryo: target hit', report.kryo.randomLastTarget || report.kryo.visibleSlow));
  results.push(checkTrue('kryo: slow applied', report.kryo.visibleSlow));

  // Sniper: pierces multiple enemies, no DoT
  results.push(checkGt('sniper: pierced count', report.sniper.pierced));
  results.push(checkTrue('sniper: no DoT (burn/poison)', report.sniper.noDot));
  if (report.sniper.highestAbsoluteHp !== undefined) {
    results.push(checkTrue('sniper: highest absolute HP targeting', report.sniper.highestAbsoluteHp));
  }

  // Mortar (redesign): fixed impact point
  results.push(checkTrue('mortar: fixed impact (witness hit, fleeer untouched)', report.mortar.fixedImpact));

  // Quantum: line hits, off-line untouched, beam created
  results.push(checkGt('quantum: line hits', report.quantum.lineHits));
  results.push(checkTrue('quantum: off-line untouched', report.quantum.offLineUntouched));
  results.push(checkTrue('quantum: beam created', report.quantum.beam));

  // Nano: spawned, damaged target, cleaned up
  results.push(checkGt('nano: spawned count', report.nano.spawned));
  results.push(checkTrue('nano: damaged target', report.nano.damaged));
  results.push(checkTrue('nano: cleaned up', report.nano.cleaned));

  return results;
}

// ─── Result Printer ────────────────────────────────────────────────
function printResults(name, results) {
  const pass = results.filter(r => r.passed).length;
  const fail = results.length - pass;
  const status = fail === 0 ? '✅' : '❌';
  console.log(`  ${status} ${name}: ${pass}/${results.length} passed${fail > 0 ? ` (${fail} FAILED)` : ''}`);
  if (fail > 0) {
    results.filter(r => !r.passed).forEach(r => {
      console.log(`     ❌ ${r.name}: actual=${JSON.stringify(r.actual)} expected=${r.expected}`);
    });
  }
}

// ─── Main ──────────────────────────────────────────────────────────
async function main() {
  console.log('═══════════════════════════════════════════════════');
  console.log(' AEGIS LABYRINTH – AUTOMATED AUDIT SUITE');
  console.log('═══════════════════════════════════════════════════');
  console.log(`  Datei: ${HTML_PATH}`);
  console.log(`  Zeit:  ${timestamp()}\n`);

  if (!fs.existsSync(HTML_PATH)) {
    console.error('❌ Aegis-Labyrinth.html nicht gefunden!');
    process.exit(1);
  }

  const result = {
    timestamp: timestamp(),
    file: HTML_PATH,
    fileHash: sha256(fs.readFileSync(HTML_PATH)),
    self: null,
    extension: null,
    redesign: null,
    assertions: { self: [], extension: [], redesign: [] },
    pageErrors: [],
    summary: { pass: 0, fail: 0, total: 0 }
  };

  let browser;

  try {
    browser = await chromium.launch({
      headless: true,
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
    const page = await context.newPage();

    const pageErrors = [];
    page.on('pageerror', (err) => pageErrors.push(err.message));

    const url = 'file:///' + HTML_PATH.replace(/\\/g, '/');
    await page.goto(url, { waitUntil: 'load', timeout: 30000 });
    await page.waitForFunction(() => typeof window.__AEGIS_DEBUG__ !== 'undefined', { timeout: 10000 });
    console.log('  ✅ Seite geladen, __AEGIS_DEBUG__ verfügbar\n');

    // ─── 1. Self-Audit ────────────────────────────────────────────────────────────
    console.log('📡 SELF-AUDIT (runSelfAudit)');
    result.self = await page.evaluate(() => {
      try {
        return window.__AEGIS_DEBUG__.runSelfAudit();
      } catch (e) {
        return { error: e.message };
      }
    });
    if (result.self.error) {
      console.log(`  ❌ Error: ${result.self.error}`);
    } else {
      result.assertions.self = assertSelfAudit(result.self);
      printResults('self', result.assertions.self);
    }
    console.log('');

    // ─── 2. Extension-Audit ──────────────────────────────────────────────────────
    console.log('🧪 EXTENSION-AUDIT (runExtensionAudit)');
    result.extension = await page.evaluate(() => {
      try {
        return window.__AEGIS_DEBUG__.runExtensionAudit();
      } catch (e) {
        return { error: e.message };
      }
    });
    if (result.extension.error) {
      console.log(`  ❌ Error: ${result.extension.error}`);
    } else {
      result.assertions.extension = assertExtensionAudit(result.extension);
      printResults('extension', result.assertions.extension);
    }
    console.log('');

    // ─── 3. Redesign-Audit ───────────────────────────────────────────────────────
    console.log('🔬 REDESIGN-AUDIT (runRedesignAudit)');
    result.redesign = await page.evaluate(() => {
      try {
        return window.__AEGIS_DEBUG__.runRedesignAudit();
      } catch (e) {
        return { error: e.message };
      }
    });
    if (result.redesign.error) {
      console.log(`  ❌ Error: ${result.redesign.error}`);
    } else {
      result.assertions.redesign = assertRedesignAudit(result.redesign);
      printResults('redesign', result.assertions.redesign);
    }
    console.log('');

    // ─── 4. Page Errors ──────────────────────────────────────────────────────────
    console.log('🐛 PAGE ERROR CHECK');
    result.pageErrors = pageErrors;
    if (pageErrors.length === 0) {
      console.log('  ✅ Keine JS-Fehler\n');
    } else {
      console.log(`  ❌ ${pageErrors.length} JS-Fehler:`);
      pageErrors.slice(0, 5).forEach(e => console.log(`     - ${e}`));
      console.log('');
    }

    // ─── Screenshot ──────────────────────────────────────────────────────────────
    const shotPath = path.join(ROOT, 'audit-final.png');
    await page.screenshot({ path: shotPath, fullPage: false }).catch(() => {});
    if (fs.existsSync(shotPath)) {
      console.log(`  📸 Screenshot: ${shotPath}`);
    }

    // ─── Summary ─────────────────────────────────────────────────────────────────
    const allAssertions = [
      ...result.assertions.self,
      ...result.assertions.extension,
      ...result.assertions.redesign
    ];
    const pass = allAssertions.filter(a => a.passed).length;
    const fail = allAssertions.length - pass;
    const total = allAssertions.length + pageErrors.length;

    result.summary = { pass, fail, total };

    console.log('═══════════════════════════════════════════════════');
    console.log(` ERGEBNIS: ✅ ${pass} PASS  ❌ ${fail} FAIL  (${allAssertions.length} Checks, ${pageErrors.length} PageErrors)`);
    console.log('═══════════════════════════════════════════════════');

    const outPath = path.join(ROOT, 'audit-result.json');
    fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf-8');
    console.log(`\n  📄 Ergebnis gespeichert: ${outPath}`);

    await browser.close();
    process.exit(fail > 0 || pageErrors.length > 0 ? 1 : 0);

  } catch (err) {
    console.error('\n❌ Audit fehlgeschlagen:', err.message);
    if (browser) await browser.close().catch(() => {});
    const outPath = path.join(ROOT, 'audit-result.json');
    fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf-8');
    process.exit(2);
  }
}

main();