import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
import { dirname, extname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const here = dirname(fileURLToPath(import.meta.url));
const outputDirectory = join(here, "outputs");
const repo = resolve(process.argv[2] || process.env.ORANGEBOARD_REPO || "../../../../orangeboard");

const readText = (relativePath) => readFile(join(repo, relativePath), "utf8");
const readJson = async (relativePath) => JSON.parse(await readText(relativePath));
const git = (...args) => execFileSync("git", args, { cwd: repo, encoding: "utf8" }).trim();
const sha256 = (value) => createHash("sha256").update(value).digest("hex");

async function filesUnder(directory) {
  const found = [];
  for (const entry of await readdir(directory, { withFileTypes: true })) {
    const fullPath = join(directory, entry.name);
    if (entry.isDirectory()) found.push(...(await filesUnder(fullPath)));
    else found.push(fullPath);
  }
  return found;
}

function numberConstant(source, name) {
  const match = source.match(new RegExp(`export const ${name} = ([0-9.]+);`));
  if (!match) throw new Error(`Could not find numeric constant ${name}.`);
  return Number(match[1]);
}

const [roadsText, billboardsText, pedestrianText, fiberText, trafficSource, saliencySource, attentionSource, owlSource, opportunitySource] = await Promise.all([
  readText("public/sf-roads.geojson"),
  readText("public/sf-billboards.geojson"),
  readText("public/sf-ped-counts.json"),
  readText("data/billboard-fiber-businesses.json"),
  readText("app/lib/trafficSim.ts"),
  readText("app/lib/saliency.ts"),
  readText("app/lib/attention.ts"),
  readText("app/lib/owlDetector.ts"),
  readText("app/api/opportunities/route.ts"),
]);

const roads = JSON.parse(roadsText);
const billboards = JSON.parse(billboardsText);
const pedestrianWeights = JSON.parse(pedestrianText);
const fiber = JSON.parse(fiberText);
const roadCounts = roads.features.reduce((counts, feature) => {
  const type = feature.properties?.type || "unknown";
  counts[type] = (counts[type] || 0) + 1;
  return counts;
}, {});

const fiberBoards = Object.values(fiber.billboards);
const businessAssociations = fiberBoards.flatMap((board) => board.businesses || []);
const uniqueBusinesses = new Set(businessAssociations.map((business) => business.name).filter(Boolean));

const logRows = git("log", "--reverse", "--format=%H%x09%aI%x09%s")
  .split(/\r?\n/)
  .filter(Boolean)
  .map((row) => {
    const [commit, authoredAt, ...subjectParts] = row.split("\t");
    return { commit, authoredAt, subject: subjectParts.join("\t") };
  });
const firstCommit = logRows[0];
const deploymentCommit = logRows.find((row) => row.subject === "feat: finalize orangeboard deployment");
if (!deploymentCommit) throw new Error("Could not find the deployment commit used to bound the hackathon build.");
const buildWindowMs = new Date(deploymentCommit.authoredAt) - new Date(firstCommit.authoredAt);

const appFiles = (await filesUnder(join(repo, "app"))).filter((file) => [".ts", ".tsx"].includes(extname(file)));
const appLineCounts = await Promise.all(appFiles.map(async (file) => {
  const source = await readFile(file, "utf8");
  return source.split(/\r?\n/).length - (/\r?\n$/.test(source) ? 1 : 0);
}));
const apiRouteCount = appFiles.filter((file) => file.replaceAll("\\", "/").includes("/app/api/") && file.endsWith("route.ts")).length;

const personaDwells = [...attentionSource.matchAll(/dwellMs:\s*(\d+)/g)].map((match) => Number(match[1])).slice(0, 4);
const routeBlock = trafficSource.slice(trafficSource.indexOf("export const BUS_ROUTES"), trafficSource.indexOf("export const BUS_COUNT"));
const syntheticBusRoutes = (routeBlock.match(/^\s*\[\[-122/gm) || []).length;

const results = {
  schemaVersion: 1,
  auditedAt: new Date().toISOString(),
  repository: {
    remote: git("remote", "get-url", "origin"),
    branch: git("branch", "--show-current"),
    commit: git("rev-parse", "HEAD"),
    commitCount: Number(git("rev-list", "--count", "HEAD")),
    workingTreeClean: git("status", "--porcelain") === "",
  },
  hackathonWindow: {
    firstCommit,
    deploymentCommit,
    elapsedMilliseconds: buildWindowMs,
    elapsedHours: Number((buildWindowMs / 3_600_000).toFixed(2)),
  },
  groundedData: {
    permittedBillboards: billboards.features.length,
    roadSegments: roads.features.length,
    vehicleRoadSegments: roadCounts.vehicle || 0,
    pedestrianRoadSegments: roadCounts.pedestrian || 0,
    pedestrianHotspots: pedestrianWeights.length,
    enrichedBillboardRecords: fiberBoards.length,
    businessAssociations: businessAssociations.length,
    uniqueBusinessNames: uniqueBusinesses.size,
    hashes: {
      roadsSha256: sha256(roadsText),
      billboardsSha256: sha256(billboardsText),
      pedestrianWeightsSha256: sha256(pedestrianText),
      businessAssociationsSha256: sha256(fiberText),
    },
  },
  runtimeModel: {
    baselinePedestrians: numberConstant(trafficSource, "PED_COUNT"),
    baselineCars: numberConstant(trafficSource, "CAR_COUNT"),
    syntheticBusRoutes,
    syntheticBusFallbackCount: syntheticBusRoutes * 2,
    fixationMilliseconds: numberConstant(saliencySource, "FIXATION_MS"),
    syntheticViewerDwellMilliseconds: personaDwells,
    attentionViewerCount: personaDwells.length,
    owlCandidateTopK: Number(owlSource.match(/topk:\s*(\d+)/)?.[1] || 0),
    owlBlankContentFloor: Number(owlSource.match(/c\.score \* \((0\.\d+)/)?.[1] || 0),
    owlContentWeight: Number(owlSource.match(/\+ (0\.\d+) \* content/)?.[1] || 0),
  },
  implementation: {
    appTypeScriptFiles: appFiles.length,
    appTypeScriptLines: appLineCounts.reduce((sum, count) => sum + count, 0),
    apiRouteCount,
    fallbackEvidence: {
      heuristicCreative: opportunitySource.includes("buildOpportunityBillboards"),
      publicTransitFallback: trafficSource.includes("syntheticBuses"),
      confidencePlusSaliencyDetection: owlSource.includes("0.15 + 0.85 * content"),
      independentGazeOverlap: attentionSource.includes("regionSaliencyShare") && attentionSource.includes("gazeOnRegion"),
    },
  },
  claimBoundary: {
    grounded: [
      "billboard coordinates and permit metadata",
      "OpenStreetMap road geometry",
      "checked-in pedestrian hotspot weights",
      "live Muni positions when the public feed responds",
      "pixel-derived saliency and gaze-region overlap",
    ],
    modeled: [
      "individual pedestrian and car paths",
      "dwell budgets for four viewer personas",
      "attention, recall, and shareability scores",
      "ICP match and opportunity scores",
    ],
    heuristicInCurrentOpportunityRoute: [
      "billboard visibilityScore is derived from opportunity fit and nearby-match count",
      "dwellSeconds is derived from visibilityScore and nearby-match count",
    ],
    notEstablished: [
      "real audience impressions",
      "human eye-tracking accuracy",
      "conversion lift or return on ad spend",
      "current billboard availability, dimensions, pricing, or ownership where seller data is absent",
    ],
  },
};

await mkdir(outputDirectory, { recursive: true });
await writeFile(join(outputDirectory, "orangeboard-audit.json"), `${JSON.stringify(results, null, 2)}\n`, "utf8");
console.log(JSON.stringify(results, null, 2));
