"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const workflow = require("../../Web/legacy-foreign-index-candle-workflow.js");

const profiles = [
  ["다우존스", "Dow", "DJI@DJI"],
  ["나스닥", "Nasdaq", "NAS@IXIC"],
  ["S&P500", "Sp500", "SPI@SPX"]
];
const periods = [
  ["5일", 5, "8061"],
  ["20일", 20, "8040"],
  ["60일", 60, "8046"],
  ["120일", 120, "8051"],
  ["240일", 240, "8056"]
];

function raw(subject = "다우존스", subtype = "5일", itemIndex = 0, enabled = true) {
  return {
    itemIndex,
    enabled,
    groupCode: "해외지수",
    subject,
    graphicType: "캔들그래프",
    subtype,
    pageText: "1/1",
    dataCode: ""
  };
}

function outcome(pending) {
  return {
    itemIndex: pending.itemIndex,
    status: "resolved",
    targetKey: pending.targetKey,
    inputName: pending.inputName,
    symbol: pending.symbol,
    nationCode: "US",
    foreignDomesticCode: "0",
    sceneCode: "s8010",
    valueType: "PRICE",
    periodDays: pending.periodDays,
    cutCode: pending.cutCode
  };
}

function response(requestId, pendingRows, rows = pendingRows.map(outcome)) {
  return {
    requestId,
    retrievedAt: "2026-07-12T12:34:56+09:00",
    totalRowCount: rows.length,
    rows
  };
}

test("all three fixed targets and five periods classify including inactive 240-day contract", () => {
  let itemIndex = 0;
  for (const [subject, targetKey, symbol] of profiles) {
    for (const [legacyPeriod, periodDays, cutCode] of periods) {
      const pending = workflow.classify(raw(subject, legacyPeriod, itemIndex), {
        id: `foreign_${itemIndex}`,
        fadeDuration: 6
      });
      assert.ok(pending);
      assert.equal(pending.targetKey, targetKey);
      assert.equal(pending.symbol, symbol);
      assert.equal(pending.periodDays, periodDays);
      assert.equal(pending.cutCode, cutCode);
      assert.equal(workflow.isPending(pending), true);
      itemIndex += 1;
    }
  }
  assert.equal(itemIndex, 15);
});

test("raw classifier is exact and preserves disabled state", () => {
  const disabled = workflow.classify(raw("다우존스", "240일", 9, false), {
    id: "foreign_disabled",
    fadeDuration: 0
  });
  assert.ok(disabled);
  assert.equal(disabled.enabled, false);
  for (const invalid of [
    { ...raw(), groupCode: "FOREIGN_INDEX" },
    { ...raw(), subject: "다우" },
    { ...raw(), graphicType: "캔들 그래프" },
    { ...raw(), subtype: "1년" },
    { ...raw(), pageText: "1/2" },
    { ...raw(), dataCode: "DJI@DJI|US|0" },
    { ...raw(), itemIndex: 1000 }
  ]) {
    assert.equal(workflow.classify(invalid, { id: "foreign_bad", fadeDuration: 6 }), null);
  }
  assert.equal(workflow.classify(raw(), { id: "foreign_bad", fadeDuration: 6, extra: true }), null);
});

test("only an exact correlated native result materializes s8010 PRICE identity", () => {
  const pending = workflow.classify(raw("S&P500", "120일", 7, false), {
    id: "foreign_materialized",
    fadeDuration: 4
  });
  const normalized = workflow.normalizeResponse(
    response("load_1", [pending]),
    "load_1",
    [pending]);
  assert.ok(normalized);
  const entry = workflow.materialize(pending, normalized.rows[0]);
  assert.ok(entry);
  assert.equal(workflow.isMaterializedEntry(entry), true);
  assert.equal(entry.builderKey, "s8010");
  assert.equal(entry.code, "8051");
  assert.equal(entry.enabled, false);
  assert.deepEqual(entry.selection, {
    groupCode: "FOREIGN_INDEX",
    subject: "S&P500",
    graphicType: "",
    subtype: "PRICE",
    dataCode: "SPI@SPX|US|0"
  });
  assert.equal(entry.operator.source, "legacy-foreign-index-candle-workflow");
});

test("identity, order, count, correlation and result-shape tampering fail closed", () => {
  const first = workflow.classify(raw("다우존스", "5일", 2), {
    id: "foreign_first", fadeDuration: 6
  });
  const second = workflow.classify(raw("나스닥", "20일", 8), {
    id: "foreign_second", fadeDuration: 6
  });
  const pending = [first, second];
  const exact = response("load_2", pending);
  assert.ok(workflow.normalizeResponse(exact, "load_2", pending));
  assert.equal(workflow.normalizeResponse(exact, "stale_load", pending), null);
  assert.equal(workflow.normalizeResponse({ ...exact, totalRowCount: 1 }, "load_2", pending), null);
  assert.equal(workflow.normalizeResponse({ ...exact, rows: [...exact.rows].reverse() }, "load_2", pending), null);
  assert.equal(workflow.normalizeResponse({ ...exact, extra: true }, "load_2", pending), null);

  for (const [key, value] of [
    ["targetKey", "Nasdaq"],
    ["inputName", "나스닥"],
    ["symbol", "NAS@IXIC"],
    ["nationCode", "KR"],
    ["foreignDomesticCode", "1"],
    ["sceneCode", "s5001"],
    ["valueType", "VOLUME"],
    ["periodDays", 20],
    ["cutCode", "8040"]
  ]) {
    const rows = [{ ...exact.rows[0], [key]: value }, exact.rows[1]];
    assert.equal(workflow.normalizeResponse({ ...exact, rows }, "load_2", pending), null, key);
  }
});

test("per-row native failures remain terminal and cannot materialize", () => {
  const pending = workflow.classify(raw(), { id: "foreign_failed", fadeDuration: 6 });
  for (const failure of [
    "INVALID_ROW", "IDENTITY_NOT_FOUND", "IDENTITY_AMBIGUOUS",
    "DATABASE_DATA_INVALID", "RESTORE_FAILED"
  ]) {
    const normalized = workflow.normalizeResponse(response("load_3", [pending], [{
      itemIndex: pending.itemIndex,
      status: "failed",
      failure
    }]), "load_3", [pending]);
    assert.ok(normalized);
    assert.equal(workflow.materialize(pending, normalized.rows[0]), null);
  }
  assert.equal(workflow.normalizeResponse(response("load_3", [pending], [{
    itemIndex: pending.itemIndex,
    status: "failed",
    failure: "RETRY"
  }]), "load_3", [pending]), null);
});

test("forged pending and forged resolved values never materialize", () => {
  const pending = workflow.classify(raw(), { id: "foreign_trusted", fadeDuration: 6 });
  const normalized = workflow.normalizeResponse(response("load_4", [pending]), "load_4", [pending]);
  assert.equal(workflow.materialize({ ...pending }, normalized.rows[0]), null);
  assert.equal(workflow.materialize(pending, { ...normalized.rows[0] }), null);
});

test("legacy signature round-trips only the exact verified entry", () => {
  const pending = workflow.classify(raw("나스닥", "240일", 3, false), {
    id: "foreign_round_trip", fadeDuration: 5
  });
  const normalized = workflow.normalizeResponse(response("load_5", [pending]), "load_5", [pending]);
  const entry = workflow.materialize(pending, normalized.rows[0]);
  const restored = workflow.restorePlaylistEntry(entry);
  assert.ok(restored);
  assert.deepEqual(workflow.legacySelectionForEntry(restored), {
    groupCode: "해외지수",
    subject: "나스닥",
    graphicType: "캔들그래프",
    subtype: "240일",
    dataCode: ""
  });
  assert.equal(workflow.matchesRaw(restored, raw("나스닥", "240일", 3, false)), true);
  assert.equal(workflow.matchesRaw(restored, raw("나스닥", "120일", 3, false)), false);
  for (const tampered of [
    { ...entry, code: "8040" },
    { ...entry, selection: { ...entry.selection, subject: "다우존스" } },
    { ...entry, operator: { ...entry.operator, symbol: "DJI@DJI" } },
    { ...entry, operator: { ...entry.operator, periodDays: 120 } },
    { ...entry, extra: true }
  ]) {
    assert.equal(workflow.restorePlaylistEntry(tampered), null);
  }
});

test("enabled replacement preserves the branded foreign-index proof immutably", () => {
  const storedRaw = raw("S&P500", "20일", 6, true);
  const pending = workflow.classify(storedRaw, {
    id: "foreign_toggle", fadeDuration: 6
  });
  const normalized = workflow.normalizeResponse(response("load_toggle", [pending]),
    "load_toggle", [pending]);
  const entry = workflow.materialize(pending, normalized.rows[0]);
  const replacement = workflow.withEnabled(entry, false);

  assert.notEqual(replacement, entry);
  assert.equal(Object.isFrozen(replacement), true);
  assert.equal(entry.enabled, true);
  assert.equal(replacement.enabled, false);
  assert.equal(workflow.isMaterializedEntry(replacement), true);
  assert.equal(workflow.matchesRaw(replacement, { ...storedRaw, enabled: false }), true);
  assert.equal(workflow.withEnabled({ ...entry }, false), null);
});

test("bridge errors are request-bound and never retryable", () => {
  const error = {
    requestId: "load_error",
    code: "PLAYOUT_PRIORITY",
    message: "송출 우선 처리로 취소했습니다.",
    retryable: false
  };
  assert.deepEqual(workflow.normalizeError(error, "load_error"), error);
  assert.equal(workflow.normalizeError({ ...error, retryable: true }, "load_error"), null);
  assert.equal(workflow.normalizeError(error, "stale"), null);
  assert.equal(workflow.normalizeError({ ...error, code: "RETRY" }, "load_error"), null);
});
