"use strict";

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

const krxTheme = Object.freeze({
  market: "krx",
  themeCode: "00000001",
  title: "AI 반도체",
  displayTitle: "AI 반도체",
  session: null,
  itemCount: 17
});
const nxtPreTheme = Object.freeze({
  market: "nxt",
  themeCode: "00000002",
  title: "로봇",
  displayTitle: "로봇(NXT)",
  session: "PRE_MARKET",
  itemCount: 121
});
const nxtAfterTheme = Object.freeze({
  ...nxtPreTheme,
  session: "AFTER_MARKET"
});

test("theme workflow exposes the UC4 and closed resolver contract", () => {
  assert.deepEqual(workflow.sourceContract, {
    originalControl: "Control/UC4.cs",
    krxResolverGroup: "PAGED_THEME",
    nxtResolverGroup: "PAGED_NXT_THEME",
    maximumPageCount: 20,
    maximumPreviewItems: 240
  });
  assert.deepEqual(workflow.themeSorts, [
    { id: "INPUT_ORDER", label: "입력순" },
    { id: "GAIN_DESC", label: "상승률순" },
    { id: "GAIN_ASC", label: "하락률순" }
  ]);
  assert.deepEqual(workflow.nxtSessions.map(value => value.id), ["PRE_MARKET", "AFTER_MARKET"]);
  assert.deepEqual(workflow.actions.map(value => [value.id, value.builderKey, value.code, value.pageSize]), [
    ["five-row-current", "s5074", "5074", 5],
    ["five-row-expected", "s5074", "5074", 5],
    ["six-row-current", "s5077", "5077", 6],
    ["twelve-row-current", "s5088", "5088", 12]
  ]);
});

test("KRX and NXT database identities normalize without inferring market from a suffix", () => {
  assert.deepEqual(workflow.normalizeTheme({
    Market: "Krx", ThemeCode: "00000001", ThemeTitle: "AI 반도체", Session: "NotApplicable",
    previewItemCount: 17
  }), krxTheme);
  assert.equal(workflow.normalizeTheme({
    market: "krx", themeCode: "123", title: "Legacy"
  }).themeCode, "123");
  assert.equal(workflow.normalizeTheme({
    market: "krx", themeCode: "0123", title: "Legacy"
  }).themeCode, "0123");
  assert.deepEqual(workflow.normalizeTheme({
    market: "NXT", themeCode: "00000002", title: "로봇", session: "PreMarket", items: new Array(121)
  }), nxtPreTheme);
  assert.deepEqual(workflow.normalizeTheme({
    market: "nxt", code: "00000002", themeTitle: "로봇", session: "after-market", itemCount: 121
  }), nxtAfterTheme);
  assert.equal(workflow.normalizeTheme({
    market: "nxt", themeCode: "00000002", title: "로봇(NXT)", session: "PRE_MARKET"
  }), null);
  assert.equal(workflow.normalizeTheme({
    market: "krx", themeCode: "00000001", title: " AI 반도체 "
  }), null);
});

test("typed theme validation reports clear market, code, session and preview failures", () => {
  assert.equal(workflow.getCompatibility("five-row-current", {
    market: "other", themeCode: "00000001", title: "AI"
  }, "INPUT_ORDER").reason, "invalid-theme-market");
  assert.equal(workflow.getCompatibility("five-row-current", {
    market: "krx", themeCode: "12", title: "AI"
  }, "INPUT_ORDER").reason, "invalid-theme-code");
  assert.equal(workflow.getCompatibility("five-row-current", {
    market: "krx", themeCode: " 00000001 ", title: "AI"
  }, "INPUT_ORDER").reason, "invalid-theme-code");
  assert.equal(workflow.getCompatibility("five-row-current", {
    market: "nxt", themeCode: "00000001", title: "AI"
  }, "INPUT_ORDER").reason, "nxt-session-required");
  assert.equal(workflow.getCompatibility("five-row-current", {
    market: "krx", themeCode: "00000001", title: "AI", session: "PRE_MARKET"
  }, "INPUT_ORDER").reason, "krx-session-must-be-empty");
  assert.equal(workflow.getCompatibility("five-row-current", {
    ...krxTheme, itemCount: 241
  }, "INPUT_ORDER").reason, "invalid-theme-item-count");
  assert.equal(workflow.getCompatibility("five-row-current", krxTheme, "RANDOM").reason,
    "invalid-theme-sort");
});

test("only three current row sizes and KRX five-row expected are compatible", () => {
  for (const actionId of ["five-row-current", "six-row-current", "twelve-row-current"]) {
    for (const sort of workflow.themeSorts) {
      assert.equal(workflow.isActionAllowed(actionId, krxTheme, sort.id), true);
      assert.equal(workflow.isActionAllowed(actionId, nxtPreTheme, sort.id), true);
      assert.equal(workflow.isActionAllowed(actionId, nxtAfterTheme, sort.id), true);
    }
  }
  assert.equal(workflow.isActionAllowed("five-row-expected", krxTheme, "INPUT_ORDER"), true);
  assert.equal(workflow.getCompatibility("five-row-expected", nxtPreTheme, "INPUT_ORDER").reason,
    "expected-theme-is-krx-only");
  assert.equal(workflow.getCompatibility("six-row-expected", krxTheme, "INPUT_ORDER").reason,
    "unknown-action");
  assert.equal(workflow.getCompatibility("five-row-current", { ...krxTheme, itemCount: 0 }, "INPUT_ORDER").reason,
    "theme-preview-is-empty");
});

test("KRX current and expected selections map sort and value kind to separate fields", () => {
  const current = workflow.buildThemeSelection("five-row-current", krxTheme, "GAIN_DESC");
  assert.equal(current.builderKey, "s5074");
  assert.equal(current.code, "5074");
  assert.deepEqual(current.aliases, ["5074"]);
  assert.deepEqual(current.selection, {
    groupCode: "PAGED_THEME",
    subject: "AI 반도체",
    graphicType: "GAIN_DESC",
    subtype: "CURRENT",
    dataCode: "00000001"
  });

  const expected = workflow.buildThemeSelection("five-row-expected", krxTheme, "GAIN_ASC");
  assert.equal(expected.builderKey, "s5074");
  assert.deepEqual(expected.selection, {
    groupCode: "PAGED_THEME",
    subject: "AI 반도체",
    graphicType: "GAIN_ASC",
    subtype: "EXPECTED",
    dataCode: "00000001"
  });
});

test("NXT selections map explicit session to GraphicType and sort to Subtype", () => {
  const five = workflow.buildThemeSelection("five-row-current", nxtPreTheme, "INPUT_ORDER");
  assert.deepEqual(five.selection, {
    groupCode: "PAGED_NXT_THEME",
    subject: "로봇(NXT)",
    graphicType: "PRE_MARKET",
    subtype: "INPUT_ORDER",
    dataCode: "00000002"
  });

  const six = workflow.buildThemeSelection("six-row-current", nxtAfterTheme, "GAIN_DESC");
  assert.equal(six.builderKey, "s5077");
  assert.equal(six.code, "5077");
  assert.equal(six.selection.graphicType, "AFTER_MARKET");
  assert.equal(six.selection.subtype, "GAIN_DESC");

  const twelve = workflow.buildThemeSelection("twelve-row-current", nxtAfterTheme, "GAIN_ASC");
  assert.equal(twelve.builderKey, "s5088");
  assert.equal(twelve.code, "5088");
  assert.deepEqual(twelve.aliases, ["5088"]);
  assert.equal(twelve.selection.subtype, "GAIN_ASC");
});

test("item-count previews calculate 5, 6 and 12 row pages within the 20-page boundary", () => {
  assert.deepEqual(workflow.calculatePagePreview("five-row-current", { ...krxTheme, itemCount: 17 }), {
    itemCount: 17, accessibleItemCount: 17, pageSize: 5,
    pageCount: 4, maximumPageCount: 20, isTruncated: false
  });
  assert.deepEqual(workflow.calculatePagePreview("five-row-current", { ...krxTheme, itemCount: 101 }), {
    itemCount: 101, accessibleItemCount: 100, pageSize: 5,
    pageCount: 20, maximumPageCount: 20, isTruncated: true
  });
  assert.deepEqual(workflow.calculatePagePreview("six-row-current", { ...krxTheme, itemCount: 120 }), {
    itemCount: 120, accessibleItemCount: 120, pageSize: 6,
    pageCount: 20, maximumPageCount: 20, isTruncated: false
  });
  assert.deepEqual(workflow.calculatePagePreview("twelve-row-current", { ...krxTheme, itemCount: 240 }), {
    itemCount: 240, accessibleItemCount: 240, pageSize: 12,
    pageCount: 20, maximumPageCount: 20, isTruncated: false
  });
  assert.deepEqual(workflow.calculatePagePreview("five-row-current", {
    market: "krx", themeCode: "00000001", title: "AI 반도체"
  }), {
    itemCount: null, accessibleItemCount: null, pageSize: 5,
    pageCount: null, maximumPageCount: 20, isTruncated: false
  });
});

test("playlist entries preserve the alias, preview and explicit theme identity", () => {
  const entry = workflow.createThemePlaylistEntry("six-row-current", nxtAfterTheme, "GAIN_DESC", {
    id: "nxt-theme", fadeDuration: 4
  });
  assert.equal(entry.builderKey, "s5077");
  assert.equal(entry.code, "5077");
  assert.equal(entry.title, "로봇(NXT)");
  assert.equal(entry.detail, "6종목 현재가 · 상승률순");
  assert.equal(entry.pageSize, 6);
  assert.equal(entry.pagePreview.pageCount, 20);
  assert.equal(entry.pagePreview.isTruncated, true);
  assert.equal(entry.fadeDuration, 4);
  assert.deepEqual(entry.operator.theme, nxtAfterTheme);
  assert.equal(entry.operator.resolverGroup, "PAGED_NXT_THEME");
});

test("trusted theme restore reconstructs fields and rejects action, identity and preview tampering", () => {
  const original = workflow.createThemePlaylistEntry("five-row-expected", krxTheme, "GAIN_ASC", {
    id: "stored-theme", fadeDuration: 8
  });
  assert.ok(workflow.restoreThemePlaylistEntry(original));
  assert.equal(workflow.restoreThemePlaylistEntry({ ...original, enabled: false }).enabled, false);
  assert.equal(workflow.restoreThemePlaylistEntry({ ...original, code: "5088" }), null);
  assert.equal(workflow.restoreThemePlaylistEntry({
    ...original,
    selection: { ...original.selection, subtype: "CURRENT" }
  }), null);
  assert.equal(workflow.restoreThemePlaylistEntry({
    ...original,
    pagePreview: { ...original.pagePreview, pageCount: 20 }
  }), null);
  assert.equal(workflow.restoreThemePlaylistEntry({
    ...original,
    operator: { ...original.operator, schemaVersion: 2 }
  }), null);
  assert.equal(workflow.restoreThemePlaylistEntry({
    ...original,
    operator: { ...original.operator, theme: { ...original.operator.theme, themeCode: "00000003" } }
  }), null);
  assert.equal(workflow.restoreThemePlaylistEntry({
    ...original,
    operator: { ...original.operator, resolverGroup: "PAGED_NXT_THEME" }
  }), null);
});

test("unknown actions, unsafe ids, invalid fades and unsupported known-empty themes cannot be created", () => {
  assert.throws(() => workflow.createThemePlaylistEntry("unknown", krxTheme, "INPUT_ORDER", {
    id: "unknown"
  }), /unknown/i);
  assert.throws(() => workflow.createThemePlaylistEntry("five-row-current", krxTheme, "INPUT_ORDER", {
    id: "bad id"
  }), /safe theme playlist id/i);
  assert.throws(() => workflow.createThemePlaylistEntry("five-row-current", krxTheme, "INPUT_ORDER", {
    id: "bad-fade", fadeDuration: 61
  }), /0 through 60/i);
  assert.throws(() => workflow.createThemePlaylistEntry(
    "five-row-current", { ...krxTheme, itemCount: 0 }, "INPUT_ORDER", { id: "empty" }),
  /theme-preview-is-empty/i);
  assert.throws(() => workflow.createThemePlaylistEntry(
    "five-row-expected", nxtPreTheme, "INPUT_ORDER", { id: "nxt-expected" }),
  /expected-theme-is-krx-only/i);
});
