"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const foreignStock = require("../../Web/named-foreign-stock-restore-workflow.js");
const legacyRows = require("../../Web/legacy-named-row-workflow.js");
const namedPlaylists = require("../../Web/named-playlist-workflow.js");

const root = path.resolve(__dirname, "../..");
const app = fs.readFileSync(path.join(root, "Web/app.js"), "utf8");
const index = fs.readFileSync(path.join(root, "Web/index.html"), "utf8");
const bridge = fs.readFileSync(
  path.join(root, "MainWindow.NamedForeignStockRestore.cs"), "utf8");
const namedBridge = fs.readFileSync(path.join(root, "MainWindow.NamedPlaylists.cs"), "utf8");
const core = fs.readFileSync(
  path.join(root, "src/MBN_STOCK_WEBVIEW.Core/Data/LegacyNamedForeignStockRestore.cs"),
  "utf8");

function rawRow(enabled = false) {
  return {
    itemIndex: 0,
    enabled,
    groupCode: "해외종목",
    subject: "NVIDIA",
    graphicType: "캔들그래프",
    subtype: "240일",
    pageText: "1/1",
    dataCode: ""
  };
}

function materializedEntry() {
  const raw = rawRow();
  const pending = foreignStock.classify(raw, {
    id: "db-foreign-stock-240",
    fadeDuration: 6,
    ma5: true,
    ma20: false
  });
  const response = foreignStock.normalizeResponse({
    requestId: "foreign-stock-load",
    retrievedAt: "2026-07-12T12:34:56+09:00",
    totalRowCount: 1,
    rows: [{
      itemIndex: 0,
      status: "resolved",
      actionId: "stock-candle-240d",
      inputName: "NVIDIA",
      symbol: "NAS@NVDA",
      nationCode: "US",
      foreignDomesticCode: "1",
      builderKey: "s8010",
      cutCode: "8056",
      valueType: "PRICE",
      periodDays: 240
    }]
  }, "foreign-stock-load", [pending]);
  return foreignStock.materialize(pending, response.rows[0]);
}

test("named load connects one correlated native foreign-stock restore batch end to end", () => {
  assert.match(index, /<script src="named-foreign-stock-restore-workflow\.js"><\/script>[\s\S]*<script src="legacy-named-row-workflow\.js"><\/script>/u);
  assert.match(app, /MbnNamedForeignStockRestoreWorkflow/u);
  assert.match(app, /namedForeignStockRestoreWorkflow\.classify/u);
  assert.match(app, /namedForeignStockRestoreWorkflow\.normalizeResponse/u);
  assert.match(app, /namedForeignStockRestoreWorkflow\.materialize/u);
  assert.match(app, /case "named-foreign-stock-restore-results"/u);
  assert.match(app, /case "named-foreign-stock-restore-error"/u);
  assert.match(namedBridge, /await PostNamedForeignStockRestoreAsync\(/u);
  assert.match(bridge, /PostMessage\("named-foreign-stock-restore-results"/u);
  assert.match(bridge, /PostMessage\("named-foreign-stock-restore-error"/u);
});

test("Core uses one exact bound SELECT and native bridge never writes or calls playout", () => {
  assert.match(core, /F_INPUT_NAME = :input_name/u);
  assert.match(core, /F_FDTC = '1'/u);
  assert.match(core, /F_NATC IN \('US', 'TW'\)/u);
  assert.match(core, /WHERE ROWNUM <= 2/u);
  assert.match(core, /new DataQueryParameter\("input_name"/u);
  assert.doesNotMatch(core, /\bLIKE\b/iu);
  assert.doesNotMatch(core, /\b(?:INSERT\s+INTO|UPDATE\s+[A-Z_]|DELETE\s+FROM|MERGE\s+INTO)\b/iu);
  assert.doesNotMatch(bridge, /Tornado|TakeIn|PrepareAsync|ExecuteNonQuery/iu);
  assert.match(bridge, /retryable = false/u);
  assert.match(bridge, /자동 재시도하지 않습니다/u);
});

test("pending state blocks edits and timeout is terminal without an automatic retry", () => {
  assert.match(app, /state\.namedPlaylist\.foreignStockRestorePending/u);
  assert.match(app, /armNamedForeignStockRestoreTimeout/u);
  assert.match(app, /setTimeout\(\(\) => \{[\s\S]*자동 재시도하지 않습니다\.[\s\S]*\}, 30000\)/u);
  assert.match(app, /comparisonRestorePending \|\|[\s\S]*foreignStockRestorePending \|\|[\s\S]*foreignIndexCandleRestorePending/u);
  assert.match(app, /pendingForeignStocks\.length === 0/u);
  assert.match(app, /clearNamedForeignStockRestoreTimeout\(record\)/u);
  assert.doesNotMatch(app, /retryNamedForeignStock|requestNamedForeignStockRestore/u);
});

test("verified identity and moving averages preserve the exact original seven fields for save", () => {
  const entry = materializedEntry();
  const raw = rawRow();
  assert.ok(entry);
  assert.equal(legacyRows.matchesCanonicalEntry(entry, raw), true);
  const selection = legacyRows.legacySelectionForEntry(entry);
  assert.deepEqual(selection, {
    groupCode: "해외종목",
    subject: "NVIDIA",
    graphicType: "캔들그래프",
    subtype: "240일",
    dataCode: ""
  });
  assert.deepEqual(entry.operator.movingAverages, { ma5: true, ma20: false });
  assert.deepEqual(namedPlaylists.toLegacySevenFields({ ...entry, selection }, "1/1"), [
    "0", "해외종목", "NVIDIA", "캔들그래프", "240일", "1/1", ""
  ]);
  assert.match(app, /해외종목 항목의 원본 7필드 서명을 안전하게 왕복할 수 없습니다/u);
  assert.match(app, /namedForeignStockRestoreWorkflow\.matchesRaw\(restoredForeignStock, rawRow\)/u);
  assert.match(app, /item\.operator\?\.source === namedForeignStockRestoreWorkflow\.source\) return item/u);
});

test("canonical FOREIGN_STOCK remains on the existing workflow and cannot collide", () => {
  assert.equal(foreignStock.classify({
    ...rawRow(true),
    groupCode: "FOREIGN_STOCK",
    graphicType: "MA5",
    subtype: "PRICE",
    dataCode: "NAS@NVDA|US|1"
  }, {
    id: "canonical-does-not-route",
    fadeDuration: 6,
    ma5: true,
    ma20: false
  }), null);
  assert.match(app, /addOverseasNamedCandidates/u);
  assert.match(app, /rawRow\.groupCode !== "FOREIGN_STOCK"/u);
});
