import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import { resolve, join } from 'node:path';
import { test } from 'node:test';
import ts from 'typescript';
import { CDCGPSF_TRANSLATIONS } from './index';
import { CDCGPSF_PAGE_LABELS } from './cdcgpsf-page-labels.seed';
import { cdcGpsfUiKey, resolveCdcGpsfUiLabels } from '../../../../frontend/features/cdc-gpsf/cdc-gpsf-ui-labels';
import { fetchDashboardDictionary } from '../../../../frontend/features/cdc-gpsf/dashboard/dashboard-translations';
import { comparisonText } from '../../../../frontend/features/cdc-gpsf/progress-report/components/semester-comparison-text';
import { rgcDecisionText } from '../../../../frontend/features/cdc/rgc-decision/data/rgc-decision-i18n';
import { combinedRgcFormText } from '../../../../frontend/features/cdc-gpsf/issue-matrix-rgc-decision/combined-rgc-form-labels';

const root = resolve(__dirname, '../../../../frontend/features/cdc-gpsf');
const seedFor = (page: string, key: string) => CDCGPSF_TRANSLATIONS.find(row => row.page === page && row.translationKey === key);

test('CDC G-PSF page labels have unique bilingual seeds and valid page parents', () => {
  const ids = CDCGPSF_TRANSLATIONS.map(row => row.page + '/' + row.translationKey);
  assert.equal(new Set(ids).size, ids.length);
  for (const [page, key, en, kh] of CDCGPSF_PAGE_LABELS) {
    const row = seedFor(page, key);
    assert.ok(row, page + '/' + key);
    assert.equal(row.module, 'cdcgpsf');
    assert.equal(row.englishText, en);
    assert.equal(row.khmerText, kh);
    assert.ok(seedFor(row.parent!.page, row.parent!.translationKey));
  }
});

test('CDC G-PSF dictionaries override all labels, preserve other values and fall back on blank or missing labels', async () => {
  const original = globalThis.fetch;
  try {
    for (const page of new Set(CDCGPSF_PAGE_LABELS.map(row => row[0]))) {
      for (const language of ['en', 'kh']) {
        globalThis.fetch = async input => {
          const url = new URL(String(input));
          assert.equal(url.searchParams.get('module'), 'cdcgpsf');
          assert.equal(url.searchParams.get('page'), page);
          assert.equal(url.searchParams.get('language'), language === 'kh' ? 'km' : 'en');
          return Response.json({ data: { data: { translations: {
            'ui.title': 'Admin title', 'ui.empty': ' ', 'ui.count': 'not a number',
          } } } });
        };
        const dictionary = await fetchDashboardDictionary('cdcgpsf', language, undefined, page);
        assert.deepEqual(resolveCdcGpsfUiLabels({ title: 'Title', empty: 'Empty', absent: 'Missing', count: 5 }, dictionary, 'ui'),
          { title: 'Admin title', empty: 'Empty', absent: 'Missing', count: 5 });
      }
    }
    for (const [page, key, en] of CDCGPSF_PAGE_LABELS) {
      const dot = key.lastIndexOf('.');
      const prefix = key.slice(0, dot), field = key.slice(dot + 1);
      assert.equal(resolveCdcGpsfUiLabels({ [field]: en }, { [key]: 'Admin ' + page }, prefix)[field], 'Admin ' + page);
    }
  } finally { globalThis.fetch = original; }
});

test('component label bindings and bilingual fallbacks match seeded API keys', () => {
  let checked = 0;
  function check(page: string, prefix: string, defaults: Record<string, unknown>) {
    for (const [key, value] of Object.entries(defaults)) {
      if (typeof value !== 'string') continue;
      const apiKey = cdcGpsfUiKey(prefix, key);
      const row = seedFor(page, apiKey);
      assert.ok(row, page + '/' + apiKey);
      assert.ok([row.englishText, row.khmerText].includes(value), page + '/' + apiKey + ': ' + value);
      checked++;
    }
  }
  function scan(dir: string) {
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
      const file = join(dir, entry.name);
      if (entry.isDirectory()) { scan(file); continue; }
      if (!/\.tsx?$/.test(file) || file.endsWith('.test.ts') || file.endsWith('.spec.tsx')) continue;
      const source = ts.createSourceFile(file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
      const variables = new Map<string, ts.Expression>();
      let messagePage = '';
      function gather(node: ts.Node) {
        if (ts.isVariableDeclaration(node) && node.initializer) variables.set(node.name.getText(source), node.initializer);
        if (ts.isCallExpression(node) && node.expression.getText(source) === 'useCdcGpsfUiTranslation' && ts.isStringLiteral(node.arguments[0])) messagePage = node.arguments[0].text;
        ts.forEachChild(node, gather);
      }
      gather(source);
      function objects(node: ts.Expression): Array<Record<string, unknown>> {
        if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) return objects(node.expression);
        if (ts.isObjectLiteralExpression(node)) return [Object.fromEntries(node.properties.flatMap(prop =>
          ts.isPropertyAssignment(prop) && ts.isStringLiteral(prop.initializer) ? [[prop.name.getText(source).replace(/^['"]|['"]$/g, ''), prop.initializer.text]] : []))];
        if (ts.isConditionalExpression(node)) return [...objects(node.whenTrue), ...objects(node.whenFalse)];
        if (ts.isBinaryExpression(node)) return [...objects(node.left), ...objects(node.right)];
        if (ts.isElementAccessExpression(node) || ts.isPropertyAccessExpression(node)) {
          const name = node.expression.getText(source);
          if (name === 'comparisonText') return [comparisonText.en, comparisonText.km];
          if (name === 'rgcDecisionText') return [rgcDecisionText.en, rgcDecisionText.kh];
          const declaration = variables.get(name);
          const object = declaration && ts.isAsExpression(declaration) ? declaration.expression : declaration;
          if (object && ts.isObjectLiteralExpression(object)) return object.properties.flatMap(prop => ts.isPropertyAssignment(prop) ? objects(prop.initializer) : []);
        }
        return [];
      }
      function visit(node: ts.Node) {
        if (ts.isCallExpression(node)) {
          const name = node.expression.getText(source);
          if (name === 'useCdcGpsfUiLabels' && ts.isStringLiteral(node.arguments[0]) && ts.isStringLiteral(node.arguments[2])) {
            for (const defaults of objects(node.arguments[1])) check(node.arguments[0].text, node.arguments[2].text, defaults);
          }
          if (name === 'uiTranslate' && messagePage && ts.isStringLiteral(node.arguments[0])) assert.ok(seedFor(messagePage, node.arguments[0].text), messagePage + '/' + node.arguments[0].text);
        }
        ts.forEachChild(node, visit);
      }
      visit(source);
    }
  }
  scan(root);
  check('issue_matrix_rgc_decision', 'rgc.legacy_form', combinedRgcFormText.en);
  check('issue_matrix_rgc_decision', 'rgc.legacy_form', combinedRgcFormText.kh);
  assert.ok(checked > 500, `Only ${checked} label fallbacks checked`);
});
