"use strict";

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

const kospi = workflow.getMarketTarget("Kospi");
const kospi200 = workflow.getMarketTarget("코스피200 지수");
const futures = workflow.getMarketTarget("futures");
const wonDollar = workflow.getMarketTarget("WonDollar");
const samsung = Object.freeze({
  kind: "krx-stock", market: "kospi", stockName: "삼성전자",
  stockCode: "005930", displayName: "삼성전자"
});
const skHynix = Object.freeze({
  kind: "krx-stock", market: "kospi", stockName: "SK하이닉스",
  stockCode: "000660", displayName: "SK하이닉스"
});
const nxtSamsung = Object.freeze({
  kind: "nxt-stock", market: "kospi", stockName: "삼성전자",
  stockCode: "005930", displayName: "삼성전자(NXT)"
});
const apple = Object.freeze({
  kind: "world-stock", inputName: "Apple Inc", displayName: "애플",
  symbol: "NAS@AAPL", nation: "US"
});

test("comparison inventory pins the exact runtime asset, 24 targets and nine actions", () => {
  assert.deepEqual(workflow.runtimeAsset, {
    relativePath: "bin/Debug/Res/종목비교.ini",
    sha256: "980F74C000EF6A34B85FDAAF6D2756269483637186C91A93548F35C5CFC1B7E4"
  });
  assert.equal(workflow.marketTargets.length, 24);
  assert.equal(new Set(workflow.marketTargets.map(value => value.target)).size, 24);
  assert.deepEqual(workflow.marketTargets.map(value => [value.displayName, value.target]), [
    ["코스피 지수", "Kospi"], ["코스닥 지수", "Kosdaq"],
    ["코스피200 지수", "Kospi200"], ["선물 지수", "Futures"],
    ["KRX100지수", "Krx100"], ["환율-원달러", "WonDollar"],
    ["환율-원엔", "WonYen"], ["환율-원위엔", "WonYuan"],
    ["환율-원유로", "WonEuro"], ["해외지수-다우", "Dow"],
    ["해외지수-나스닥", "Nasdaq"], ["해외지수-S&P", "Sp500"],
    ["해외지수-독일", "GermanyDax"], ["해외지수-영국", "UnitedKingdomFtse"],
    ["해외지수-프랑스", "FranceCac"], ["해외지수-일본", "Nikkei"],
    ["해외지수-홍콩", "HangSeng"], ["해외지수-대만", "TaiwanWeighted"],
    ["해외지수-싱가포르", "SingaporeStraitsTimes"], ["해외지수-태국", "ThailandSet"],
    ["해외지수-필리핀", "PhilippinesComposite"], ["해외지수-말레이시아", "MalaysiaKlse"],
    ["해외지수-인도네시아", "IndonesiaComposite"], ["해외지수-중국", "ShanghaiComposite"]
  ]);
  assert.deepEqual(workflow.actions.map(value => value.label), [
    "2열판 예상체결", "2열판", "종목별 비교분석_캔들 그래프", "종목별 비교분석_라인 그래프",
    "5일", "1개월", "3개월", "6개월", "12개월"
  ]);
  assert.deepEqual(workflow.yieldActionIds, [
    "return-5d", "return-1m", "return-3m", "return-6m", "return-12m"
  ]);
});

test("typed market, KRX, NXT and world targets normalize without heuristic aliases", () => {
  assert.equal(workflow.getMarketTarget("코스피200 지수"), kospi200);
  assert.equal(workflow.getMarketTarget("sp500").target, "Sp500");
  assert.deepEqual(workflow.normalizeTarget({
    kind: "krx-stock", market: "KOSPI", name: " 삼성전자 ", code: "005930"
  }), samsung);
  assert.deepEqual(workflow.normalizeTarget({
    kind: "nxt-stock", market: "KOSPI", name: "삼성전자", code: "005930"
  }), nxtSamsung);
  assert.deepEqual(workflow.normalizeTarget(apple), apple);
  assert.equal(workflow.subjectTokenForTarget(kospi200), "Kospi200");
  assert.equal(workflow.subjectTokenForTarget(nxtSamsung), "삼성전자(NXT)");
  assert.equal(workflow.subjectTokenForTarget(apple), "Apple Inc");
  assert.equal(workflow.targetKey(nxtSamsung), "nxt:kospi:005930");

  assert.equal(workflow.normalizeTarget({ ...kospi, displayName: "코스피" }), null);
  assert.equal(workflow.normalizeTarget({ ...samsung, stockName: "삼성,전자" }), null);
  assert.equal(workflow.normalizeTarget({ ...nxtSamsung, displayName: "삼성전자" }), null);
  assert.equal(workflow.normalizeTarget({ ...apple, nation: "JP" }), null);
  assert.equal(workflow.normalizeTarget({ ...apple, symbol: "AAPL;DROP" }), null);
});

test("target double-click rotation, swap and clear preserve the original slot rules", () => {
  const empty = workflow.clearPair();
  assert.deepEqual(empty, [null, null]);
  const first = workflow.rotatePair(empty, samsung);
  assert.deepEqual(first, [samsung, null]);
  const second = workflow.rotatePair(first, skHynix);
  assert.deepEqual(second, [skHynix, samsung]);
  assert.deepEqual(workflow.swapPair(second), [samsung, skHynix]);
  assert.deepEqual(workflow.rotatePair(second, skHynix), [skHynix, skHynix]);
  assert.equal(workflow.normalizePair(first), null);
});

test("CURRENT compatibility mirrors supported s8018 and s5032 branches and fails closed", () => {
  assert.equal(workflow.isActionAllowed("two-column-current", [kospi, kospi200]), true);
  assert.equal(workflow.isActionAllowed("two-column-current", [kospi, samsung]), true);
  assert.equal(workflow.isActionAllowed("two-column-current", [kospi, nxtSamsung]), true);
  assert.equal(workflow.isActionAllowed("two-column-current", [samsung, nxtSamsung]), true);
  assert.equal(workflow.isActionAllowed("two-column-current", [apple, samsung]), true);
  assert.equal(workflow.isActionAllowed("two-column-current", [apple, nxtSamsung]), true);

  assert.equal(workflow.getCompatibility("two-column-current", [kospi, apple]).reason,
    "mixed-market-and-world-is-unsupported");
  assert.equal(workflow.getCompatibility("two-column-current", [futures, samsung]).reason,
    "futures-requires-a-market-target-counterpart");
  assert.equal(workflow.getCompatibility("two-column-current", [futures, futures]).reason,
    "two-futures-targets-are-unsupported");
  assert.equal(workflow.isActionAllowed("two-column-current", [futures, kospi]), true);
});

test("EXPECTED and graph compatibility expose only combinations the Core can resolve", () => {
  assert.equal(workflow.isActionAllowed("two-column-expected", [kospi, samsung]), true);
  assert.equal(workflow.isActionAllowed("two-column-expected", [samsung, skHynix]), true);
  assert.equal(workflow.isActionAllowed("two-column-expected", [wonDollar, samsung]), false);
  assert.equal(workflow.isActionAllowed("two-column-expected", [nxtSamsung, samsung]), false);
  assert.equal(workflow.isActionAllowed("two-column-expected", [apple, samsung]), false);

  const futuresExpected = workflow.getCompatibility("two-column-expected", [futures, wonDollar]);
  assert.equal(futuresExpected.supported, true);
  assert.equal(futuresExpected.effectiveMode, "CURRENT");
  assert.match(futuresExpected.warning, /current s5032/i);

  for (const actionId of ["comparison-candle", "comparison-line", ...workflow.yieldActionIds]) {
    assert.equal(workflow.isActionAllowed(actionId, [samsung, skHynix]), true);
    assert.equal(workflow.isActionAllowed(actionId, [samsung, nxtSamsung]), false);
    assert.equal(workflow.isActionAllowed(actionId, [samsung, apple]), false);
    assert.equal(workflow.isActionAllowed(actionId, [kospi, samsung]), false);
  }
});

test("two-column builders use canonical market subjects and the correct dynamic alias", () => {
  const current = workflow.createComparisonPlaylistEntry("two-column-current", [kospi200, samsung], {
    id: "current-pair"
  });
  assert.equal(current.builderKey, "s8018");
  assert.equal(current.code, "8032");
  assert.deepEqual(current.aliases, ["8032"]);
  assert.deepEqual(current.selection, {
    groupCode: "INDEX", subject: "Kospi200,삼성전자",
    graphicType: "2열판", subtype: "CURRENT", dataCode: ""
  });

  const stockPair = workflow.buildComparisonSelection("two-column-current", [nxtSamsung, apple]);
  assert.equal(stockPair.builderKey, "s8018");
  assert.equal(stockPair.selection.groupCode, "STOCK");
  assert.equal(stockPair.selection.subject, "삼성전자(NXT),Apple Inc");

  const futuresPair = workflow.createComparisonPlaylistEntry("two-column-expected", [wonDollar, futures], {
    id: "futures-pair", fadeDuration: 0
  });
  assert.equal(futuresPair.builderKey, "s5032");
  assert.equal(futuresPair.code, "5032");
  assert.equal(futuresPair.selection.subtype, "EXPECTED");
  assert.equal(futuresPair.effectiveMode, "CURRENT");
  assert.equal(futuresPair.fadeDuration, 0);
});

test("candle, line and all five return actions create explicit selections in legacy order", () => {
  const candle = workflow.createComparisonPlaylistEntry("comparison-candle", [samsung, skHynix], {
    id: "candle"
  });
  assert.equal(candle.builderKey, "s5026");
  assert.equal(candle.code, "5026");
  assert.deepEqual(candle.selection, {
    groupCode: "STOCK", subject: "삼성전자,SK하이닉스",
    graphicType: "COMPARISON_CANDLE", subtype: "FiveDays",
    dataCode: "KOSPI:005930|KOSPI:000660"
  });

  const line = workflow.createComparisonPlaylistEntry("comparison-line", [samsung, skHynix], {
    id: "line"
  });
  assert.equal(line.builderKey, "s5087");
  assert.equal(line.selection.graphicType, "COMPARISON_LINE");
  assert.equal(line.selection.dataCode, "KOSPI:005930|KOSPI:000660");

  const batch = workflow.createYieldBatchEntries([samsung, skHynix], {
    idPrefix: "pair-yield", fadeDuration: 4
  });
  assert.equal(batch.length, 5);
  assert.deepEqual(batch.map(value => value.id), [
    "pair-yield-5d", "pair-yield-1m", "pair-yield-3m", "pair-yield-6m", "pair-yield-12m"
  ]);
  assert.deepEqual(batch.map(value => value.selection.subtype), [
    "FiveDays", "OneMonth", "ThreeMonths", "SixMonths", "TwelveMonths"
  ]);
  assert.ok(batch.every(value => value.builderKey === "s5029" && value.code === "5029"));
});

test("trusted restore reconstructs selections and rejects stored-entry tampering", () => {
  const original = workflow.createComparisonPlaylistEntry("two-column-current", [kospi, nxtSamsung], {
    id: "stored", fadeDuration: 7
  });
  assert.ok(workflow.restoreComparisonPlaylistEntry(original));
  assert.equal(workflow.restoreComparisonPlaylistEntry({ ...original, enabled: false }).enabled, false);
  assert.equal(workflow.restoreComparisonPlaylistEntry({ ...original, code: "5032" }), null);
  assert.equal(workflow.restoreComparisonPlaylistEntry({
    ...original, selection: { ...original.selection, subject: "Kospi,삼성전자" }
  }), null);
  assert.equal(workflow.restoreComparisonPlaylistEntry({
    ...original, operator: { ...original.operator, assetSha256: "0".repeat(64) }
  }), null);
  assert.equal(workflow.restoreComparisonPlaylistEntry({
    ...original, operator: { ...original.operator, schemaVersion: 2 }
  }), null);
  assert.equal(workflow.restoreComparisonPlaylistEntry({
    ...original,
    operator: {
      ...original.operator,
      pair: [{ ...kospi, displayName: "변조" }, nxtSamsung]
    }
  }), null);
});

test("saved pair list normalization is schema-versioned, bounded and duplicate-safe", () => {
  const first = workflow.createPairRecord("first", [samsung, skHynix]);
  const second = workflow.createPairRecord("second", [kospi, nxtSamsung]);
  assert.ok(first);
  assert.equal(workflow.createPairRecord("bad id", [samsung, skHynix]), null);
  assert.deepEqual(workflow.normalizePairList({ schemaVersion: 2, pairs: [first] }), {
    schemaVersion: 1, pairs: []
  });

  const normalized = workflow.normalizePairList({
    schemaVersion: 1,
    pairs: [first, { id: "invalid", pair: [samsung, null] }, first, second]
  });
  assert.deepEqual(normalized.pairs.map(value => value.id), ["first", "second"]);
  assert.ok(Object.isFrozen(normalized));
  assert.ok(Object.isFrozen(normalized.pairs));

  const appended = workflow.appendPair(normalized, "third", [apple, nxtSamsung]);
  assert.deepEqual(appended.pairs.map(value => value.id), ["first", "second", "third"]);
  assert.deepEqual(normalized.pairs.map(value => value.id), ["first", "second"]);
  assert.throws(() => workflow.appendPair(appended, first), /already exists/i);
});

test("saved pair rows reorder and delete persistently even at the empty-list boundary", () => {
  let list = workflow.deleteAllPairs();
  list = workflow.appendPair(list, "first", [samsung, skHynix]);
  list = workflow.appendPair(list, "second", [kospi, nxtSamsung]);
  list = workflow.appendPair(list, "third", [apple, samsung]);

  assert.deepEqual(workflow.reorderPairList(list, "first", "up").pairs.map(value => value.id),
    ["first", "second", "third"]);
  list = workflow.reorderPairList(list, "third", -1);
  assert.deepEqual(list.pairs.map(value => value.id), ["first", "third", "second"]);
  list = workflow.reorderPairList(list, "first", "down");
  assert.deepEqual(list.pairs.map(value => value.id), ["third", "first", "second"]);
  list = workflow.deletePair(list, "first");
  assert.deepEqual(list.pairs.map(value => value.id), ["third", "second"]);
  list = workflow.deleteAllPairs();
  assert.deepEqual(list, { schemaVersion: 1, pairs: [] });
});

test("unsafe ids, fades, incomplete pairs and unsupported combinations cannot create entries", () => {
  assert.throws(() => workflow.createComparisonPlaylistEntry("two-column-current", [samsung, null], {
    id: "missing"
  }), /two comparison targets/i);
  assert.throws(() => workflow.createComparisonPlaylistEntry("two-column-current", [kospi, apple], {
    id: "unsupported"
  }), /mixed-market-and-world/i);
  assert.throws(() => workflow.createComparisonPlaylistEntry("two-column-current", [samsung, skHynix], {
    id: "bad id"
  }), /safe comparison playlist id/i);
  assert.throws(() => workflow.createComparisonPlaylistEntry("two-column-current", [samsung, skHynix], {
    id: "bad-fade", fadeDuration: 61
  }), /0 through 60/i);
  assert.equal(workflow.getCompatibility("unknown", [samsung, skHynix]).reason, "unknown-action");
});
