/**
 * Fixture drift detection.
 *
 * Each unit fixture that was sourced from a syntax-test file has an entry in
 * .manifest.json recording the source path and a SHA-256 hash of the source
 * at the time the fixture was last verified.
 *
 * This test suite checks each entry for two conditions:
 *
 *   ORPHAN — the source file no longer exists (renamed, deleted, moved).
 *   DRIFT  — the source file exists but its content has changed since the
 *             manifest was last updated.
 *
 * Both conditions cause the test to FAIL so they appear in the summary at the
 * end of the test run and are never silently missed.
 *
 * To resolve a DRIFT failure:
 *   1. Review the change in the source file.
 *   2. If the change is safe to adopt, run:
 *        node scripts/sync-fixture.js "<fixture-path>"
 *      This re-copies the fixture and updates the manifest hash.
 *   3. If the fixture should intentionally differ, update .manifest.json
 *      manually: set sourceHash to the current source hash and add a "note"
 *      explaining the intentional divergence.
 *
 * To resolve an ORPHAN failure:
 *   - If the source was renamed: update the "source" path in .manifest.json.
 *   - If the source was deleted and the fixture is still valid: remove the
 *     manifest entry (the fixture becomes fixture-only, maintained manually).
 *   - If the fixture is no longer needed: delete it and its manifest entry.
 *
 * Fixtures not in the manifest (block-structure/, function-user-defined/,
 * hash-mode/, output-lines/) have no known syntax-test source and are
 * maintained manually.
 */

import * as crypto from "crypto";
import * as fs from "fs";
import * as path from "path";

import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const manifestPath = path.join(__dirname, "fixtures", ".manifest.json");
const testBase = path.resolve(__dirname, ".."); // architect-language-tools/test/

const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));

describe("Fixture drift", () => {
  manifest.fixtures.forEach((entry) => {
    const sourcePath = path.join(testBase, entry.source.replace(/\//g, path.sep));

    if (!fs.existsSync(sourcePath)) {
      test(entry.fixture, () => {
        throw new Error(
          `ORPHAN — source no longer exists.\n` +
          `  Fixture : ${entry.fixture}\n` +
          `  Source  : ${entry.source}\n` +
          `  Action  : Update .manifest.json — see file header for options.\n` +
          (entry.note ? `  Note    : ${entry.note}\n` : "")
        );
      });
      return;
    }

    const currentHash = crypto
      .createHash("sha256")
      .update(fs.readFileSync(sourcePath))
      .digest("hex")
      .toUpperCase();

    if (currentHash !== entry.sourceHash) {
      test(entry.fixture, () => {
        throw new Error(
          `DRIFT — source has changed since the manifest was last updated.\n` +
          `  Fixture      : ${entry.fixture}\n` +
          `  Source       : ${entry.source}\n` +
          `  Stored hash  : ${entry.sourceHash}\n` +
          `  Current hash : ${currentHash}\n` +
          `  Action       : To replace the unit test fixture with the updated syntax test, run:\n` +
          `                   node scripts/sync-fixture.js "${entry.fixture}"\n` +
          `                 Note that if the syntax-test has undergone significant change, the unit\n` +
          `                 test may need to be rewritten, or pointed at a different fixture source.\n` +
          (entry.note ? `  Note         : ${entry.note}\n` : "")
        );
      });
      return;
    }

    test(entry.fixture, () => {
      expect(currentHash).toBe(entry.sourceHash);
    });
  });
});

describe("Fixture coverage", () => {
  const fixtureBase = path.join(__dirname, "fixtures");
  const tracked = new Set(manifest.fixtures.map((e) => e.fixture));
  const fixtureOnly = new Set(manifest.fixtureOnly || []);

  function listTxtFiles(dir, base) {
    const entries = fs.readdirSync(dir, { withFileTypes: true });
    const results = [];
    for (const entry of entries) {
      if (entry.isDirectory()) {
        results.push(...listTxtFiles(path.join(dir, entry.name), base));
      } else if (entry.name.endsWith(".TXT")) {
        results.push(path.join(dir, entry.name).replace(base + path.sep, "").replace(/\\/g, "/"));
      }
    }
    return results;
  }

  const allFixtures = listTxtFiles(fixtureBase, fixtureBase);

  allFixtures.forEach((fixture) => {
    if (tracked.has(fixture) || fixtureOnly.has(fixture)) return;

    test(fixture, () => {
      throw new Error(
        `UNCOVERED — fixture has no manifest entry.\n` +
        `  Fixture : ${fixture}\n` +
        `  Action  : Add an entry to .manifest.json:\n` +
        `            - If this fixture has a syntax-test source, add it to the "fixtures" array\n` +
        `              with the source path and run: node scripts/sync-fixture.js "${fixture}"\n` +
        `            - If this fixture has no syntax-test source, add it to the "fixtureOnly" array.`
      );
    });
  });
});

