#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { join, resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const REPO_ROOT = resolve(dirname(__filename), '..');
const POLICY_PATH = join(REPO_ROOT, 'LICENSE_POLICY.json');
const NOTICES_PATH = join(REPO_ROOT, 'THIRD_PARTY_LICENSES.md');

const LICENSE_FILE_PATTERNS = [
  /^LICEN[SC]E(\.\w+)?$/i,
  /^COPYING(\.\w+)?$/i,
  /^NOTICE(\.\w+)?$/i,
];

const mode = process.argv[2] ?? 'check';

if (!['check', 'generate'].includes(mode)) {
  console.error(`Usage: licenses.mjs [check|generate]`);
  process.exit(2);
}

const policy = JSON.parse(readFileSync(POLICY_PATH, 'utf8'));
const allowedSet = new Set(policy.allowed);
const waivedSet = new Set(policy.waived.map((w) => `${w.package}::${w.license}`));

const raw = execFileSync('pnpm', ['licenses', 'list', '--prod', '--json'], {
  cwd: REPO_ROOT,
  encoding: 'utf8',
  maxBuffer: 64 * 1024 * 1024,
});
const grouped = JSON.parse(raw);

function isPlatformSpecific(paths) {
  for (const p of paths) {
    const pkgJson = join(p, 'package.json');
    if (!existsSync(pkgJson)) continue;
    try {
      const meta = JSON.parse(readFileSync(pkgJson, 'utf8'));
      if (Array.isArray(meta.os) && meta.os.length > 0) return true;
      if (Array.isArray(meta.cpu) && meta.cpu.length > 0) return true;
    } catch (err) {
      console.warn(`licenses: could not read ${pkgJson}: ${err instanceof Error ? err.message : String(err)}`);
    }
  }
  return false;
}

const packages = [];
for (const [licenseGroup, entries] of Object.entries(grouped)) {
  for (const entry of entries) {
    if (entry.name?.startsWith('@getmunin/')) continue;
    if (isPlatformSpecific(entry.paths ?? [])) continue;
    packages.push({
      name: entry.name,
      versions: entry.versions ?? [],
      paths: entry.paths ?? [],
      license: entry.license ?? licenseGroup,
      author: entry.author ?? '',
      homepage: entry.homepage ?? '',
      description: entry.description ?? '',
    });
  }
}

packages.sort((a, b) => a.name.localeCompare(b.name) || a.versions[0]?.localeCompare(b.versions[0] ?? ''));

function expressionAllowed(expr) {
  if (!expr) return false;
  const cleaned = expr.replace(/[()]/g, ' ').trim();
  if (allowedSet.has(cleaned)) return true;

  if (/\bOR\b/i.test(cleaned)) {
    return cleaned
      .split(/\bOR\b/i)
      .map((s) => s.trim())
      .some(expressionAllowed);
  }
  if (/\bAND\b/i.test(cleaned)) {
    return cleaned
      .split(/\bAND\b/i)
      .map((s) => s.trim())
      .every(expressionAllowed);
  }
  return allowedSet.has(cleaned);
}

const violations = [];
for (const p of packages) {
  if (expressionAllowed(p.license)) continue;
  const waiveKey = `${p.name}::${p.license}`;
  if (waivedSet.has(waiveKey)) continue;
  violations.push(p);
}

function findLicenseFile(dir) {
  if (!existsSync(dir) || !statSync(dir).isDirectory()) return null;
  for (const entry of readdirSync(dir)) {
    if (LICENSE_FILE_PATTERNS.some((re) => re.test(entry))) {
      const full = join(dir, entry);
      try {
        if (statSync(full).isFile()) return full;
      } catch {
        /* ignore */
      }
    }
  }
  return null;
}

function renderNotices() {
  const lines = [];
  lines.push('# Third-party licenses');
  lines.push('');
  lines.push(
    'This file lists open-source dependencies bundled with Munin runtime artifacts, ' +
      'together with their license notices. It is generated by `pnpm licenses:generate` ' +
      'from `pnpm licenses list --prod --json` and verified in CI.',
  );
  lines.push('');
  lines.push(`Generated against ${packages.length} production dependencies.`);
  lines.push('');
  lines.push('---');
  lines.push('');

  for (const p of packages) {
    lines.push(`## ${p.name}@${p.versions.join(', ')}`);
    lines.push('');
    if (p.description) lines.push(`> ${p.description}`);
    if (p.homepage) lines.push(`- Homepage: ${p.homepage}`);
    if (p.author) lines.push(`- Author: ${p.author}`);
    lines.push(`- License: ${p.license}`);
    lines.push('');
    const licenseFile = p.paths.map(findLicenseFile).find(Boolean);
    if (licenseFile) {
      const body = readFileSync(licenseFile, 'utf8').trim();
      lines.push('```');
      lines.push(body);
      lines.push('```');
    } else {
      lines.push('_No LICENSE file shipped in the package; refer to homepage above._');
    }
    lines.push('');
    lines.push('---');
    lines.push('');
  }

  return lines.join('\n');
}

const notices = renderNotices();

if (mode === 'generate') {
  writeFileSync(NOTICES_PATH, notices);
  console.log(`Wrote ${NOTICES_PATH} (${packages.length} packages).`);
}

if (mode === 'check') {
  let failed = false;

  if (violations.length > 0) {
    failed = true;
    console.error('License policy violations:');
    for (const v of violations) {
      console.error(`  - ${v.name}@${v.versions.join(',')} (${v.license})`);
    }
    console.error('');
    console.error('Add an explicit waiver in LICENSE_POLICY.json or replace the dependency.');
  }

  if (existsSync(NOTICES_PATH)) {
    const current = readFileSync(NOTICES_PATH, 'utf8');
    if (current !== notices) {
      failed = true;
      console.error(
        `THIRD_PARTY_LICENSES.md is out of date. Run \`pnpm licenses:generate\` and commit.`,
      );
    }
  } else {
    failed = true;
    console.error('THIRD_PARTY_LICENSES.md is missing. Run `pnpm licenses:generate` and commit.');
  }

  if (failed) process.exit(1);
  console.log(`License check passed: ${packages.length} production dependencies, all approved.`);
}
