import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import path from 'node:path';

export const RETAIL_BUNDLE_MAPS_DIAGNOSTIC_SCHEMA =
  'q4-retail-bundle-maps-diagnostic-v1';

export function parseRetailBundleMapDiagnosticMaps(raw) {
  if (raw === undefined) return null;
  assert.equal(typeof raw, 'string',
    'Q4_BUNDLE_MAPS_E2E_MAPS must be a comma-separated string');
  const maps = raw.split(',').map((entry) => entry.trim());
  assert.ok(maps.length > 0 && maps.every(Boolean),
    'Q4_BUNDLE_MAPS_E2E_MAPS must name at least one map and contain no empty entries');
  assert.equal(new Set(maps).size, maps.length,
    'Q4_BUNDLE_MAPS_E2E_MAPS must not contain duplicate maps');
  return maps;
}

export function selectRetailBundleMapRun(catalog, menuMaps, requestedMaps) {
  assert.ok(Array.isArray(catalog?.maps), 'retail bundle catalog maps are missing');
  assert.ok(Array.isArray(menuMaps), 'multiplayer menu maps are missing');
  if (requestedMaps === null) {
    return {
      diagnostic: false,
      catalog,
      menuMaps,
      selectedMapIds: menuMaps.map(({ id }) => id),
    };
  }

  const requested = new Set(requestedMaps);
  const menuIds = new Set(menuMaps.map(({ id }) => id));
  const catalogIds = new Set(catalog.maps.map(({ id }) => id));
  for (const id of requestedMaps) {
    assert.ok(menuIds.has(id), `diagnostic map ${id} is absent from the multiplayer menu`);
    assert.ok(catalogIds.has(id), `diagnostic map ${id} is absent from the retail bundle graph`);
  }

  const selectedMenuMaps = menuMaps.filter(({ id }) => requested.has(id));
  const selectedMapIds = selectedMenuMaps.map(({ id }) => id);
  assert.equal(selectedMapIds.length, requestedMaps.length,
    'diagnostic map selection is incomplete');
  return {
    diagnostic: true,
    catalog: {
      ...catalog,
      maps: catalog.maps.filter(({ id }) => requested.has(id)),
    },
    menuMaps: selectedMenuMaps,
    selectedMapIds,
  };
}

export function retailBundleMapRunOutputPaths(requestedMaps, {
  root = process.cwd(),
} = {}) {
  const canonicalEvidencePath = path.resolve(root, '.selfcheck/retail-bundle-maps-e2e.json');
  const canonicalOutDir = path.resolve(root, '.selfcheck/shots/retail-bundle-maps');
  if (requestedMaps === null) {
    return {
      diagnostic: false,
      evidencePath: canonicalEvidencePath,
      outDir: canonicalOutDir,
    };
  }

  const normalized = [...requestedMaps].sort();
  const selectionHash = createHash('sha256')
    .update(normalized.join('\n'))
    .digest('hex')
    .slice(0, 16);
  const runName = `retail-bundle-maps-${normalized.length}-${selectionHash}`;
  const diagnosticsRoot = path.resolve(root, '.selfcheck/diagnostics');
  const evidencePath = path.join(diagnosticsRoot, `${runName}.json`);
  const outDir = path.join(diagnosticsRoot, 'shots', runName);
  for (const candidate of [evidencePath, outDir]) {
    const relative = path.relative(diagnosticsRoot, candidate);
    assert.ok(relative && !relative.startsWith('..') && !path.isAbsolute(relative),
      'diagnostic bundle-map output escaped .selfcheck/diagnostics');
  }
  assert.notEqual(evidencePath, canonicalEvidencePath,
    'diagnostic bundle-map evidence must not overwrite publication evidence');
  assert.notEqual(outDir, canonicalOutDir,
    'diagnostic bundle-map screenshots must not overwrite publication screenshots');
  return { diagnostic: true, evidencePath, outDir };
}

export function assertRetailBundleMapDiagnosticEvidence(evidence, menuMaps) {
  assert.equal(evidence?.schema, RETAIL_BUNDLE_MAPS_DIAGNOSTIC_SCHEMA,
    'retail bundle map diagnostic evidence has the wrong schema');
  assert.equal(evidence?.diagnostic, true,
    'retail bundle map diagnostic evidence must be explicitly marked diagnostic');
  assert.deepEqual(
    evidence.selectedMaps,
    menuMaps.map(({ id, modes }) => ({ id, modes: [...modes] })),
    'retail bundle map diagnostic evidence has the wrong map selection'
  );
  const expectedScenarios = menuMaps.flatMap(({ id, modes }) =>
    modes.map((mode) => `${id}::${mode}`));
  assert.deepEqual(
    evidence.scenarios?.map(({ scenario }) => scenario),
    expectedScenarios,
    'retail bundle map diagnostic evidence does not cover every selected map/mode pair'
  );
  assert.equal(evidence.returned?.scenario, expectedScenarios[0],
    'retail bundle map diagnostic evidence did not return to its first scenario');
  return evidence;
}
