"use strict";

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

const digest = "A".repeat(64);
const retrievedAt = "2026-07-12T08:00:00+09:00";
const samsung = Object.freeze({
  kind: "krx-stock", market: "kospi", stockName: "삼성전자", stockCode: "005930"
});
const nxtSamsung = Object.freeze({
  kind: "nxt-stock", market: "kospi", stockName: "삼성전자", stockCode: "005930"
});
const nvidia = Object.freeze({
  kind: "world-stock", inputName: "엔비디아", symbol: "NAS@NVDA", nation: "US"
});

function response(pairs = [
  { rowNumber: 1, first: samsung, second: nxtSamsung },
  {
    rowNumber: 2,
    first: { kind: "market-target", target: "Kosdaq" },
    second: nvidia
  }
]) {
  return {
    requestId: "import-request-1",
    sourceSha256: digest,
    sourceRowCount: pairs.length,
    retrievedAt,
    pairs
  };
}

test("strict import response normalizes all four trusted target shapes", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");

  assert.ok(normalized);
  assert.equal(normalized.sourceRowCount, 2);
  assert.equal(normalized.pairs[0].pair[0].displayName, "삼성전자");
  assert.equal(normalized.pairs[0].pair[1].displayName, "삼성전자(NXT)");
  assert.equal(normalized.pairs[1].pair[0], comparison.getMarketTarget("Kosdaq"));
  assert.deepEqual(normalized.pairs[1].pair[1], {
    ...nvidia,
    displayName: "엔비디아"
  });
  assert.ok(Object.isFrozen(normalized));
  assert.ok(Object.isFrozen(normalized.pairs));
});

test("response validation rejects correlation, property, digest, row and target tampering", () => {
  const valid = response();
  assert.equal(workflow.normalizeImportResponse(valid, "wrong-request"), null);
  assert.equal(workflow.normalizeImportResponse({ ...valid, extra: true }, valid.requestId), null);
  assert.equal(workflow.normalizeImportResponse({ ...valid, sourceSha256: digest.toLowerCase() }, valid.requestId), null);
  assert.equal(workflow.normalizeImportResponse({ ...valid, sourceRowCount: 3 }, valid.requestId), null);
  assert.equal(workflow.normalizeImportResponse({
    ...valid,
    pairs: [{ ...valid.pairs[0], rowNumber: 2 }, valid.pairs[1]]
  }, valid.requestId), null);
  assert.equal(workflow.normalizeImportResponse({
    ...valid,
    pairs: [{ ...valid.pairs[0], first: { ...samsung, displayName: "삼성전자" } }, valid.pairs[1]]
  }, valid.requestId), null);
  assert.equal(workflow.normalizeImportResponse({
    ...valid,
    pairs: [valid.pairs[0], { ...valid.pairs[1], second: { ...nvidia, nation: "KR" } }]
  }, valid.requestId), null);
});

test("duplicate source pairs fail closed before local persistence", () => {
  const duplicate = response([
    { rowNumber: 1, first: samsung, second: nxtSamsung },
    { rowNumber: 2, first: samsung, second: nxtSamsung }
  ]);
  assert.equal(workflow.normalizeImportResponse(duplicate, duplicate.requestId), null);
});

test("merge appends deterministic records without replacing current local pairs", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  let current = comparison.deleteAllPairs();
  current = comparison.appendPair(
    current,
    "existing",
    [comparison.getMarketTarget("Kospi"), samsung]);

  const merged = workflow.mergeImport(current, normalized);

  assert.deepEqual(merged.pairs.map(value => value.id), [
    "existing",
    "legacy-compare-aaaaaaaaaaaa-1",
    "legacy-compare-aaaaaaaaaaaa-2"
  ]);
  assert.equal(current.pairs.length, 1);
  assert.equal(merged.pairs[2].pair[1].symbol, "NAS@NVDA");
});

test("merge rejects duplicate existing pairs and the 500-pair boundary atomically", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  let duplicate = comparison.deleteAllPairs();
  duplicate = comparison.appendPair(duplicate, "already-present", normalized.pairs[0].pair);
  assert.throws(() => workflow.mergeImport(duplicate, normalized), /existing pair/i);
  assert.equal(duplicate.pairs.length, 1);

  let full = comparison.deleteAllPairs();
  for (let index = 0; index < 499; index += 1) {
    full = comparison.appendPair(full, `pair-${index}`, [
      comparison.getMarketTarget(index % 2 ? "Kospi" : "Kosdaq"),
      { ...samsung, stockCode: String(index).padStart(6, "0") }
    ]);
  }
  assert.throws(() => workflow.mergeImport(full, normalized), /cannot fit/i);
  assert.equal(full.pairs.length, 499);
});

test("merge independently rejects unnormalized count and row-order input", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  const empty = comparison.deleteAllPairs();
  assert.throws(() => workflow.mergeImport(empty, {
    ...normalized,
    sourceRowCount: 1
  }), /cannot fit/i);
  assert.throws(() => workflow.mergeImport(empty, {
    ...normalized,
    pairs: [{ ...normalized.pairs[0], rowNumber: 2 }, normalized.pairs[1]]
  }), /row order/i);
});

test("one-time marker is schema-versioned, strict and digest-bound", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  const marker = workflow.createMarker(normalized, "2026-07-12T00:00:00.000Z");
  assert.deepEqual(marker, {
    schemaVersion: 1,
    sourceSha256: digest,
    importedPairCount: 2,
    importedAt: "2026-07-12T00:00:00.000Z"
  });
  assert.deepEqual(workflow.normalizeMarker(JSON.parse(JSON.stringify(marker))), marker);
  assert.equal(workflow.normalizeMarker({ ...marker, schemaVersion: 2 }), null);
  assert.equal(workflow.normalizeMarker({ ...marker, sourceSha256: "0".repeat(63) }), null);
  assert.equal(workflow.normalizeMarker({ ...marker, extra: true }), null);
});

test("pair list and one-time marker form one strict crash-consistent envelope", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  const merged = workflow.mergeImport(comparison.deleteAllPairs(), normalized);
  const marker = workflow.createMarker(normalized, "2026-07-12T00:00:00.000Z");
  const envelope = workflow.createStorageEnvelope(merged, marker);

  assert.ok(envelope);
  assert.equal(envelope.schemaVersion, workflow.storageEnvelopeSchemaVersion);
  assert.equal(envelope.pairList.pairs.length, 2);
  assert.equal(envelope.legacyImportMarker.sourceSha256, digest);
  assert.deepEqual(
    workflow.normalizeStorageEnvelope(JSON.parse(JSON.stringify(envelope))),
    envelope);
});

test("a legacy-import record without its marker is fail-closed as a split commit", () => {
  const normalized = workflow.normalizeImportResponse(response(), "import-request-1");
  const merged = workflow.mergeImport(comparison.deleteAllPairs(), normalized);

  assert.equal(workflow.createStorageEnvelope(merged, null), null);
  assert.equal(workflow.normalizeStorageEnvelope({
    schemaVersion: workflow.storageEnvelopeSchemaVersion,
    pairList: merged,
    legacyImportMarker: null
  }), null);
});
