"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const workflow = require("../../Web/candle-options-workflow.js");
const stock = require("../../Web/operator-workflow.js");
const fixed = require("../../Web/legacy-fixed-workflow.js");
const industry = require("../../Web/industry-workflow.js");
const overseas = require("../../Web/overseas-workflow.js");
const namedForeignStock = require("../../Web/named-foreign-stock-restore-workflow.js");
const tradingHalt = require("../../Web/trading-halt-workflow.js");

const adapters = { stock, fixed, industry, overseas, tradingHalt };
const options = id => ({ id, fadeDuration: 6, ma5: false, ma20: false });

const domesticStock = Object.freeze({
  market: "kospi",
  stockName: "삼성전자",
  displayName: "삼성전자",
  stockCode: "005930",
  isNxt: false
});
const industryItem = Object.freeze({ market: "kospi", name: "전기전자", code: "013" });
const overseasStock = Object.freeze({
  inputName: "NVIDIA",
  symbol: "NAS@NVDA",
  nationCode: "US",
  fdtc: "1"
});
const haltedStock = Object.freeze({
  market: "kospi",
  stockCode: "005930",
  displayName: "삼성전자"
});

function entries() {
  const fixedAction = fixed.actions.find(value => value.builderKey === "s8010" && value.available);
  const industryCut = industry.cuts.find(value => value.kind === "candle");
  const pendingNamedForeign = namedForeignStock.classify({
    itemIndex: 0,
    enabled: false,
    groupCode: "해외종목",
    subject: "NVIDIA",
    graphicType: "캔들그래프",
    subtype: "120일",
    pageText: "1/1",
    dataCode: ""
  }, options("named-overseas-candle"));
  const namedResponse = namedForeignStock.normalizeResponse({
    requestId: "named-overseas-load",
    retrievedAt: "2026-07-12T12:34:56+09:00",
    totalRowCount: 1,
    rows: [{
      itemIndex: 0,
      status: "resolved",
      actionId: "stock-candle-120d",
      inputName: "NVIDIA",
      symbol: "NAS@NVDA",
      nationCode: "US",
      foreignDomesticCode: "1",
      builderKey: "s8010",
      cutCode: "8051",
      valueType: "PRICE",
      periodDays: 120
    }]
  }, "named-overseas-load", [pendingNamedForeign]);
  const namedForeignEntry = namedForeignStock.materialize(
    pendingNamedForeign,
    namedResponse.rows[0]);
  return [
    stock.createStockPlaylistEntry(domesticStock, "candle-120d", options("stock-candle")),
    fixed.createFixedPlaylistEntry(fixedAction.id, options("fixed-candle")),
    industry.createIndustryPlaylistEntry("kospi", industryCut.id, {
      ...options("industry-candle"),
      selected: industryItem
    }),
    overseas.createOverseasStockPlaylistEntry(
      overseasStock,
      "stock-candle-120d",
      options("overseas-candle")),
    namedForeignEntry,
    tradingHalt.createTradingHaltPlaylistEntry(
      haltedStock,
      "candle-120d",
      options("halt-candle"))
  ];
}

test("global flags map to the exact closed candle graphic type", () => {
  assert.equal(workflow.expectedGraphicType(false, false), "");
  assert.equal(workflow.expectedGraphicType(true, false), "MA5");
  assert.equal(workflow.expectedGraphicType(false, true), "MA20");
  assert.equal(workflow.expectedGraphicType(true, true), "MA5,MA20");
  assert.throws(() => workflow.expectedGraphicType("yes", true));
});

test("all original and DB-restored candle entry sources can be recreated with global flags", () => {
  for (const entry of entries()) {
    const recreated = workflow.recreateCandleEntry(entry, true, true, adapters);
    assert.ok(recreated, entry.operator.source);
    assert.equal(recreated.selection.graphicType, "MA5,MA20");
    assert.deepEqual(recreated.operator.movingAverages, { ma5: true, ma20: true });
    assert.equal(recreated.enabled, entry.enabled);
    if (entry.operator.source === namedForeignStock.source) {
      assert.equal(recreated.operator.source, namedForeignStock.source);
      assert.deepEqual(recreated.operator.rawSelection, entry.operator.rawSelection);
      assert.ok(namedForeignStock.restorePlaylistEntry(recreated));
      assert.deepEqual(namedForeignStock.legacySelectionForEntry(recreated), {
        groupCode: "해외종목",
        subject: "NVIDIA",
        graphicType: "캔들그래프",
        subtype: "120일",
        dataCode: ""
      });
      assert.equal(namedForeignStock.matchesRaw(recreated, {
        itemIndex: 0,
        enabled: false,
        groupCode: "해외종목",
        subject: "NVIDIA",
        graphicType: "캔들그래프",
        subtype: "120일",
        pageText: "1/1",
        dataCode: ""
      }), true);
    }
  }
});

test("playlist synchronization changes only s8010 entries and preserves order", () => {
  const candles = entries();
  const nonCandle = stock.createStockPlaylistEntry(domesticStock, "basic-current", {
    id: "non-candle",
    fadeDuration: 6,
    ma5: false,
    ma20: false
  });
  const input = [candles[0], nonCandle, ...candles.slice(1)];

  const result = workflow.synchronizePlaylist(input, true, false, adapters);

  assert.equal(result.valid, true);
  assert.equal(result.changed, true);
  assert.equal(result.playlist[1], nonCandle);
  assert.deepEqual(result.playlist.map(value => value.id), input.map(value => value.id));
  for (const entry of result.playlist.filter(value => value.builderKey === "s8010")) {
    assert.equal(entry.selection.graphicType, "MA5");
  }
});

test("an untrusted s8010 entry blocks synchronization instead of being rewritten", () => {
  const untrusted = {
    id: "unknown-candle",
    builderKey: "s8010",
    code: "8051",
    enabled: true,
    fadeDuration: 6,
    selection: { graphicType: "" },
    operator: { source: "unknown" }
  };
  const result = workflow.synchronizePlaylist([untrusted], true, true, adapters);
  assert.equal(result.valid, false);
  assert.equal(result.changed, false);
  assert.deepEqual(result.invalidIds, ["unknown-candle"]);
  assert.equal(result.playlist[0], untrusted);
});

test("a second synchronization with identical flags is stable", () => {
  const first = workflow.synchronizePlaylist(entries(), true, true, adapters);
  const second = workflow.synchronizePlaylist([...first.playlist], true, true, adapters);
  assert.equal(first.valid, true);
  assert.equal(second.valid, true);
  assert.equal(second.changed, false);
  second.playlist.forEach((entry, index) => assert.equal(entry, first.playlist[index]));
});
