import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';

const root = path.resolve(import.meta.dirname, '..');
const checker = path.join(root, 'tools/check-entitydef-parity.py');

function writePack(archive, entries) {
  const tree = fs.mkdtempSync(path.join(path.dirname(archive), '.pack-tree-'));
  try {
    for (const [name, contents] of Object.entries(entries)) {
      const destination = path.join(tree, ...name.split('/'));
      fs.mkdirSync(path.dirname(destination), { recursive: true });
      fs.writeFileSync(destination, contents);
    }
    execFileSync('zip', ['-q', '-X', '-r', archive, '.'], { cwd: tree });
  } finally {
    fs.rmSync(tree, { recursive: true, force: true });
  }
}

function fixture(t, { nativePak0, browserPak0, sharedPak }) {
  const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'q4-entitydef-parity-'));
  t.after(() => fs.rmSync(temp, { recursive: true, force: true }));
  const nativeDir = path.join(temp, 'native');
  const browserDir = path.join(temp, 'browser');
  fs.mkdirSync(nativeDir);
  fs.mkdirSync(browserDir);

  writePack(path.join(nativeDir, 'pak0.pk4'), nativePak0);
  writePack(path.join(nativeDir, 'pak1.pk4'), { 'README.txt': 'native pak1' });
  writePack(path.join(browserDir, 'pak0.pk4'), browserPak0);
  writePack(path.join(browserDir, 'pak1.pk4'), { 'README.txt': 'browser pak1' });

  const args = [
    checker,
    '--native-pack', path.join(nativeDir, 'pak0.pk4'),
    '--native-pack', path.join(nativeDir, 'pak1.pk4'),
    '--browser-pack', path.join(browserDir, 'pak0.pk4'),
    '--browser-pack', path.join(browserDir, 'pak1.pk4')
  ];
  if (sharedPak) {
    const addon = path.join(temp, 'q4web_test-aaaaaaaaaaaa.pk4');
    writePack(addon, sharedPak);
    args.push('--shared-pack', addon);
  }
  return spawnSync('python3', args, { cwd: root, encoding: 'utf8' });
}

test('entityDef parity matches case-insensitive virtual paths and approved addons', (t) => {
  const result = fixture(t, {
    nativePak0: {
      'DEF/B.def': 'entityDef Beta { }\n',
      'def/a.def': 'entityDef Alpha { }\n'
    },
    browserPak0: {
      'def/b.DEF': 'entityDef beta { }\n',
      'Def/A.DEF': 'entityDef alpha { }\n'
    },
    sharedPak: {
      'def/q4web.def': 'entityDef addon_shared { }\n'
    }
  });

  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /entityDef parity ok: 3 declarations in 3 virtual def files/);
});

test('entityDef parity rejects a native-only declaration', (t) => {
  const result = fixture(t, {
    nativePak0: {
      'def/runtime.def': 'entityDef shared { }\nentityDef server_only { }\n'
    },
    browserPak0: {
      'def/runtime.def': 'entityDef shared { }\n'
    }
  });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /entityDef parity mismatch/);
  assert.match(result.stderr, /server_only/);
});

test('entityDef parity rejects declarations with the same names in a different order', (t) => {
  const result = fixture(t, {
    nativePak0: {
      'def/runtime.def': 'entityDef first { }\nentityDef second { }\n'
    },
    browserPak0: {
      'def/runtime.def': 'entityDef second { }\nentityDef first { }\n'
    }
  });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /runtime\.def: first, second/);
  assert.match(result.stderr, /runtime\.def: second, first/);
});

test('entityDef parity ignores comments, strings, and mapDef declarations', (t) => {
  const result = fixture(t, {
    nativePak0: {
      'def/runtime.def': [
        '// entityDef comment_only { }',
        '/* entityDef block_comment_only { } */',
        'mapDef mp/test {',
        '  "note" "entityDef string_only { }"',
        '  entityDef nested_map_token { }',
        '}',
        'entityDef real_decl { }',
        ''
      ].join('\n')
    },
    browserPak0: {
      'DEF/RUNTIME.DEF': [
        '// the comments intentionally differ',
        'mapDef mp/test { "entityDef" "not_a_decl" }',
        'entityDef REAL_DECL { }',
        ''
      ].join('\n')
    },
    sharedPak: {
      'def/q4web_map.def': 'mapDef mp/q4web { "name" "entityDef is text" }\n'
    }
  });

  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /entityDef parity ok: 1 declarations in 1 virtual def files/);
  assert.doesNotMatch(result.stdout, /comment_only|string_only|nested_map_token/);
});
