const test = require("node:test");
const assert = require("node:assert/strict");
const workflow = require("../../Web/named-nxt-theme-restore-workflow.js");

function raw(overrides = {}) {
  return {
    itemIndex: 4,
    enabled: true,
    groupCode: "테마_NXT",
    subject: "반도체(NXT)",
    graphicType: "5단 표그래프",
    subtype: "테마-현재가(입력순)",
    pageText: "1/9",
    dataCode: "0123",
    ...overrides
  };
}

function resolved(overrides = {}) {
  return {
    itemIndex: 4,
    status: "resolved",
    actionId: "five-row-current",
    builderKey: "s5074",
    cutCode: "5074",
    pageSize: 5,
    sort: "INPUT_ORDER",
    session: "PRE_MARKET",
    themeTitle: "반도체",
    storedThemeCode: "0123",
    currentThemeCode: "87654321",
    previewItemCount: 7,
    liveItemCount: 6,
    previewIsTruncated: false,
    codeWasRemapped: true,
    ...overrides
  };
}

function response(row) {
  return {
    requestId: "load-1",
    retrievedAt: "2026-07-12T12:59:00+09:00",
    totalRowCount: 1,
    rows: [row]
  };
}

test("classify routes every exact NXT theme group row for native fail-closed verification", () => {
  const pending = workflow.classify(raw({
    subject: "",
    graphicType: "unsupported",
    subtype: "",
    pageText: "",
    dataCode: ""
  }), { id: "db-4", fadeDuration: 6 });
  assert.equal(workflow.isPending(pending), true);
  assert.equal(pending.rawSelection.groupCode, "테마_NXT");
  assert.equal(workflow.classify(raw({ groupCode: "테마" }), { id: "db-4", fadeDuration: 6 }), null);
});

test("normalizeResponse accepts one exact correlated current identity", () => {
  const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
  const result = workflow.normalizeResponse(response(resolved()), "load-1", [pending]);
  assert.equal(result.rows[0].currentThemeCode, "87654321");
  assert.equal(result.rows[0].liveItemCount, 6);
});

test("normalizeResponse rejects expanded, stale, uncorrelated, or impossible identities", () => {
  const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
  assert.equal(workflow.normalizeResponse(response({ ...resolved(), extra: true }), "load-1", [pending]), null);
  assert.equal(workflow.normalizeResponse(response(resolved({ storedThemeCode: "999" })), "load-1", [pending]), null);
  assert.equal(workflow.normalizeResponse(response(resolved({ themeTitle: "다른제목" })), "load-1", [pending]), null);
  assert.equal(workflow.normalizeResponse(response(resolved({ liveItemCount: 8 })), "load-1", [pending]), null);
  assert.equal(workflow.normalizeResponse(response(resolved({ previewItemCount: 241, previewIsTruncated: false })), "load-1", [pending]), null);
});

test("failed rows preserve a closed no-retry reason", () => {
  const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
  for (const failure of [
    "INVALID_ROW", "UNSUPPORTED_ACTION", "IDENTITY_NOT_FOUND", "IDENTITY_AMBIGUOUS",
    "PREVIEW_EMPTY", "LIVE_ITEMS_EMPTY", "DATABASE_DATA_INVALID", "RESTORE_FAILED"
  ]) {
    const result = workflow.normalizeResponse(response({
      itemIndex: 4,
      status: "failed",
      failure
    }), "load-1", [pending]);
    assert.equal(result.rows[0].failure, failure);
  }
  assert.equal(workflow.normalizeError({
    requestId: "load-1",
    code: "DATABASE_UNAVAILABLE",
    message: "재검증 실패",
    retryable: false
  }, "load-1").retryable, false);
});

test("materialize uses only the current code while retaining the old raw identity for lossless save", () => {
  const sourceRaw = raw({ enabled: false });
  const pending = workflow.classify(sourceRaw, { id: "db-4", fadeDuration: 6 });
  const outcome = workflow.normalizeResponse(response(resolved()), "load-1", [pending]).rows[0];
  const entry = workflow.materialize(pending, outcome);

  assert.equal(workflow.isMaterializedEntry(entry), true);
  assert.equal(entry.enabled, false);
  assert.equal(entry.selection.groupCode, "PAGED_NXT_THEME");
  assert.equal(entry.selection.dataCode, "87654321");
  assert.equal(entry.operator.theme.themeCode, "87654321");
  assert.equal(entry.pagePreview.itemCount, 6);
  assert.deepEqual(workflow.legacySelectionForEntry(entry), {
    groupCode: "테마_NXT",
    subject: "반도체(NXT)",
    graphicType: "5단 표그래프",
    subtype: "테마-현재가(입력순)",
    dataCode: "0123"
  });
  assert.equal(workflow.matchesRaw(entry, sourceRaw), true);
});

test("dynamic NXT session changes exactly at local 13:00 without losing native proof", () => {
  const sourceRaw = raw();
  const pending = workflow.classify(sourceRaw, { id: "db-4", fadeDuration: 6 });
  const outcome = workflow.normalizeResponse(response(resolved()), "load-1", [pending]).rows[0];
  const entry = workflow.materialize(pending, outcome);
  const atTwelve = workflow.refreshDynamicSession(entry, new Date(2026, 6, 12, 12, 59));
  const atThirteen = workflow.refreshDynamicSession(atTwelve, new Date(2026, 6, 12, 13, 0));

  assert.equal(atTwelve.selection.graphicType, "PRE_MARKET");
  assert.equal(atThirteen.selection.graphicType, "AFTER_MARKET");
  assert.equal(workflow.isMaterializedEntry(atThirteen), true);
  assert.equal(workflow.matchesRaw(atThirteen, sourceRaw), true);
});

test("enabled replacement is immutable and retains NXT proof across session refresh", () => {
  const sourceRaw = raw({ enabled: true });
  const pending = workflow.classify(sourceRaw, { id: "db-toggle", fadeDuration: 6 });
  const outcome = workflow.normalizeResponse(response(resolved({ itemIndex: 4 })),
    "load-1", [pending]).rows[0];
  const entry = workflow.materialize(pending, outcome);
  const replacement = workflow.withEnabled(entry, false);
  const refreshed = workflow.refreshDynamicSession(replacement, new Date(2026, 6, 12, 13, 0));

  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(refreshed, { ...sourceRaw, enabled: false }), true);
  assert.equal(workflow.withEnabled({ ...entry }, false), null);
});

test("materialization refuses a failed outcome and non-pending objects", () => {
  const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
  assert.equal(workflow.materialize(pending, {
    itemIndex: 4,
    status: "failed",
    failure: "LIVE_ITEMS_EMPTY"
  }), null);
  assert.equal(workflow.materialize({}, resolved()), null);
});
