import fs from "node:fs";
import { execSync } from "node:child_process";

// License groups based on Google licenseclassifier categories
// https://github.com/google/licenseclassifier/blob/e6a9bb99b5a6f71d5a34336b8245e305f5430f99/license_type.go
export const licenseGroups = {
  // https://github.com/google/licenseclassifier/blob/e6a9bb99b5a6f71d5a34336b8245e305f5430f99/license_type.go#L225
  reciprocal: [
    "APSL-1.0",
    "APSL-1.1",
    "APSL-1.2",
    "APSL-2.0",
    "CDDL-1.0",
    "CDDL-1.1",
    "CPL-1.0",
    "EPL-1.0",
    "EPL-2.0",
    "FreeImage",
    "IPL-1.0",
    "MPL-1.0",
    "MPL-1.1",
    "MPL-2.0",
    "Ruby",
  ],
  // https://github.com/google/licenseclassifier/blob/e6a9bb99b5a6f71d5a34336b8245e305f5430f99/license_type.go#L249
  notice: [
    "AFL-1.1",
    "AFL-1.2",
    "AFL-2.0",
    "AFL-2.1",
    "AFL-3.0",
    "Apache-1.0",
    "Apache-1.1",
    "Apache-2.0",
    "Artistic-1.0-cl8",
    "Artistic-1.0-Perl",
    "Artistic-1.0",
    "Artistic-2.0",
    "BSL-1.0",
    "BSD-2-Clause-FreeBSD",
    "BSD-2-Clause-NetBSD",
    "BSD-2-Clause",
    "BSD-3-Clause-Attribution",
    "BSD-3-Clause-Clear",
    "BSD-3-Clause-LBNL",
    "BSD-3-Clause",
    "BSD-4-Clause",
    "BSD-4-Clause-UC",
    "BSD-Protection",
    "CC-BY-1.0",
    "CC-BY-2.0",
    "CC-BY-2.5",
    "CC-BY-3.0",
    "CC-BY-4.0",
    "FTL",
    "ISC",
    "ImageMagick",
    "Libpng",
    "Lil-1.0",
    "Linux-OpenIB",
    "LPL-1.02",
    "LPL-1.0",
    "MS-PL",
    "MIT",
    "NCSA",
    "OpenSSL",
    "PHP-3.01",
    "PHP-3.0",
    "PIL",
    "Python-2.0",
    "Python-2.0-complete",
    "PostgreSQL",
    "SGI-B-1.0",
    "SGI-B-1.1",
    "SGI-B-2.0",
    "Unicode-DFS-2015",
    "Unicode-DFS-2016",
    "Unicode-TOU",
    "UPL-1.0",
    "W3C-19980720",
    "W3C-20150513",
    "W3C",
    "X11",
    "Xnet",
    "Zend-2.0",
    "zlib-acknowledgement",
    "Zlib",
    "ZPL-1.1",
    "ZPL-2.0",
    "ZPL-2.1",
  ],
  // https://github.com/google/licenseclassifier/blob/e6a9bb99b5a6f71d5a34336b8245e305f5430f99/license_type.go#L324
  unencumbered: ["CC0-1.0", "Unlicense", "0BSD"],
} as const;

export type LicenseGroup = keyof typeof licenseGroups;

interface LicenseConfig {
  groups: LicenseGroup[];
  allow?: string[];
  deny?: string[];
}

function buildAllowSet(config: LicenseConfig): Set<string> {
  const set = new Set<string>();
  for (const group of config.groups) {
    for (const license of licenseGroups[group]) {
      set.add(license);
    }
  }
  if (config.allow) {
    for (const license of config.allow) {
      set.add(license);
    }
  }
  if (config.deny) {
    for (const license of config.deny) {
      set.delete(license);
    }
  }
  return set;
}

function isLicenseAllowed(licenseString: string, allowSet: Set<string>): boolean {
  if (/\s+(?:OR|AND)\s+/i.test(licenseString)) {
    const licenses = licenseString
      .replace(/[()]/g, "")
      .trim()
      .split(/\s+(?:OR|AND)\s+/i)
      .map((l) => l.trim())
      .filter((l) => l.length > 0);
    return licenses.every((l) => allowSet.has(l));
  }
  return allowSet.has(licenseString);
}

export interface LicenseViolation {
  package: string;
  license: string;
}

export function runLicenseList(): number {
  for (const [group, licenses] of Object.entries(licenseGroups)) {
    console.log(`${group} (${licenses.length} licenses):`);
    for (const license of licenses) {
      console.log(`  ${license}`);
    }
    console.log();
  }
  return 0;
}

export function runLicenseCheck(configPath: string): number {
  const raw = fs.readFileSync(configPath, "utf-8");
  const config = JSON.parse(raw) as LicenseConfig;

  const validGroups = Object.keys(licenseGroups);
  for (const group of config.groups) {
    if (!validGroups.includes(group)) {
      console.error(`Unknown license group: "${group}". Valid groups: ${validGroups.join(", ")}`);
      return 2;
    }
  }

  const allowSet = buildAllowSet(config);

  console.log("Checking licenses...\n");
  execSync("pnpm licenses list", { stdio: "inherit" });

  const output = execSync("pnpm licenses list --json");
  const licensesJson = JSON.parse(output.toString()) as Record<string, { name: string }[]>;

  const violations: LicenseViolation[] = [];
  for (const [license, packages] of Object.entries(licensesJson)) {
    if (!isLicenseAllowed(license, allowSet)) {
      for (const pkg of packages) {
        violations.push({ package: pkg.name, license });
      }
    }
  }

  if (violations.length === 0) {
    console.log("All licenses are allowed.");
    return 0;
  }

  console.error("Found dependencies with disallowed licenses:\n");
  for (const violation of violations) {
    console.error(`  - ${violation.package}`);
    console.error(`    License: ${violation.license}\n`);
  }
  return 1;
}
