/**
 * @file Round-trip correctness for the full-dataset export/import (backup /
 * restore) in server/lib/data-transfer.js.
 *
 * Verifies the two product requirements:
 *   1. Export captures ALL user data (sessions, agents, events, token_usage,
 *      workflows, dashboard_runs, alert_rules, model_pricing) with a
 *      format/version stamp.
 *   2. Re-importing that export reproduces the data accurately, is idempotent
 *      (re-import skips existing sessions, never duplicates), and merges cleanly
 *      when consolidating another machine's sessions into an existing DB.
 *
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const path = require("path");
const fs = require("fs");
const os = require("os");

const TEST_DB = path.join(os.tmpdir(), `dashboard-data-transfer-${Date.now()}-${process.pid}.db`);
process.env.DASHBOARD_DB_PATH = TEST_DB;

const dbModule = require("../db");
const { db, stmts } = dbModule;
const {
  buildExportBundle,
  importExportBundle,
  EXPORT_FORMAT,
  EXPORT_VERSION,
} = require("../lib/data-transfer");

after(() => {
  if (db) db.close();
  for (const suffix of ["", "-wal", "-shm"]) {
    try {
      fs.unlinkSync(TEST_DB + suffix);
    } catch {
      /* ignore */
    }
  }
});

function seedSession(id, { events = 2 } = {}) {
  db.prepare(
    "INSERT INTO sessions (id, name, status, cwd, model, started_at, ended_at) VALUES (?,?,?,?,?,?,?)"
  ).run(
    id,
    `Session ${id}`,
    "completed",
    "/tmp/x",
    "claude-opus-4-8",
    "2026-06-01T00:00:00.000Z",
    "2026-06-01T01:00:00.000Z"
  );

  const mainId = `agent_main_${id}`;
  const subId = `agent_sub_${id}`;
  db.prepare(
    "INSERT INTO agents (id, session_id, name, type, status, started_at) VALUES (?,?,?,?,?,?)"
  ).run(mainId, id, "Main", "main", "completed", "2026-06-01T00:00:00.000Z");
  // Child references parent — exercises deferred-FK ordering on restore.
  db.prepare(
    "INSERT INTO agents (id, session_id, name, type, subagent_type, status, started_at, parent_agent_id) VALUES (?,?,?,?,?,?,?,?)"
  ).run(subId, id, "Sub", "subagent", "explorer", "completed", "2026-06-01T00:10:00.000Z", mainId);

  for (let i = 0; i < events; i++) {
    db.prepare(
      "INSERT INTO events (session_id, agent_id, event_type, tool_name, summary, created_at) VALUES (?,?,?,?,?,?)"
    ).run(id, mainId, "PostToolUse", "Bash", `evt ${i}`, `2026-06-01T00:0${i}:00.000Z`);
  }

  db.prepare(
    "INSERT INTO token_usage (session_id, model, input_tokens, output_tokens, baseline_input) VALUES (?,?,?,?,?)"
  ).run(id, "claude-opus-4-8", 1000, 500, 250);

  db.prepare(
    "INSERT INTO workflows (run_id, session_id, name, status, agent_count, total_tokens) VALUES (?,?,?,?,?,?)"
  ).run(`wf_${id}`, id, "wf", "completed", 2, 1500);
}

describe("data-transfer export/import round-trip", () => {
  before(() => {
    // Config-like rows (independent of sessions).
    db.prepare(
      "INSERT INTO dashboard_runs (id, session_id, mode, cwd, status) VALUES (?,?,?,?,?)"
    ).run("run_1", "S1", "headless", "/tmp/x", "completed");
    db.prepare("INSERT INTO alert_rules (id, name, rule_type, config) VALUES (?,?,?,?)").run(
      "rule_1",
      "My Rule",
      "inactivity",
      "{}"
    );
    stmts.upsertPricing.run("custom-model-*", "Custom", 3, 15, 0.3, 3.75, 6, 0, 0);

    seedSession("S1", { events: 3 });
    seedSession("S2", { events: 2 });
  });

  it("exports every table with a format/version stamp", () => {
    const bundle = buildExportBundle(db, stmts);
    assert.equal(bundle.format, EXPORT_FORMAT);
    assert.equal(bundle.version, EXPORT_VERSION);
    assert.ok(bundle.exported_at);
    assert.equal(bundle.sessions.length, 2);
    assert.equal(bundle.agents.length, 4);
    assert.equal(bundle.events.length, 5);
    assert.equal(bundle.token_usage.length, 2);
    assert.equal(bundle.workflows.length, 2);
    assert.ok(bundle.dashboard_runs.some((r) => r.id === "run_1"));
    assert.ok(bundle.alert_rules.some((r) => r.id === "rule_1"));
    assert.ok(bundle.model_pricing.some((p) => p.model_pattern === "custom-model-*"));
  });

  it("restores an exported bundle accurately into a fresh DB", () => {
    const bundle = JSON.parse(JSON.stringify(buildExportBundle(db, stmts)));

    // Simulate a fresh machine: wipe everything the bundle carries.
    db.exec(
      "DELETE FROM events; DELETE FROM token_usage; DELETE FROM workflows; DELETE FROM agents; DELETE FROM sessions; DELETE FROM dashboard_runs; DELETE FROM alert_rules; DELETE FROM model_pricing;"
    );

    const c = importExportBundle(db, bundle);
    assert.equal(c.sessions_imported, 2);
    assert.equal(c.sessions_skipped, 0);
    assert.equal(c.agents, 4);
    assert.equal(c.events, 5);
    assert.equal(c.token_usage, 2);
    assert.equal(c.workflows, 2);
    assert.equal(c.dashboard_runs, 1);
    assert.equal(c.alert_rules, 1);
    // model_pricing includes the seeded default rows plus our custom one; all
    // are restored into the wiped DB.
    assert.equal(c.model_pricing, bundle.model_pricing.length);
    assert.ok(
      db.prepare("SELECT 1 FROM model_pricing WHERE model_pattern = 'custom-model-*'").get()
    );

    // Accuracy: token totals (incl. baseline) restored verbatim.
    const tu = db.prepare("SELECT * FROM token_usage WHERE session_id = 'S1'").get();
    assert.equal(tu.input_tokens, 1000);
    assert.equal(tu.baseline_input, 250);

    // Parent/child agent link survived deferred-FK restore.
    const sub = db.prepare("SELECT parent_agent_id FROM agents WHERE id = 'agent_sub_S1'").get();
    assert.equal(sub.parent_agent_id, "agent_main_S1");
  });

  it("is idempotent — re-importing skips existing sessions, no duplicate events", () => {
    const bundle = buildExportBundle(db, stmts);
    const eventsBefore = db.prepare("SELECT COUNT(*) c FROM events").get().c;

    const c = importExportBundle(db, bundle);
    assert.equal(c.sessions_imported, 0);
    assert.equal(c.sessions_skipped, 2);
    assert.equal(c.events, 0);

    const eventsAfter = db.prepare("SELECT COUNT(*) c FROM events").get().c;
    assert.equal(eventsAfter, eventsBefore, "events must not be duplicated on re-import");
  });

  it("merges a second machine's sessions without touching existing ones", () => {
    // Current DB has S1, S2. Build a bundle that adds a brand-new session S3.
    const bundle = buildExportBundle(db, stmts);
    seedSession("S3", { events: 4 });
    const merged = buildExportBundle(db, stmts);
    // Roll the seeded S3 back out so the DB looks like the "target" (S1,S2)
    // and the bundle is the "source" (S1,S2,S3).
    db.exec(
      "DELETE FROM events WHERE session_id='S3'; DELETE FROM token_usage WHERE session_id='S3'; DELETE FROM workflows WHERE session_id='S3'; DELETE FROM agents WHERE session_id='S3'; DELETE FROM sessions WHERE id='S3';"
    );
    void bundle;

    const c = importExportBundle(db, merged);
    assert.equal(c.sessions_imported, 1, "only the new session imports");
    assert.equal(c.sessions_skipped, 2, "existing sessions are skipped");
    assert.equal(db.prepare("SELECT COUNT(*) c FROM sessions").get().c, 3);
    assert.equal(db.prepare("SELECT COUNT(*) c FROM events WHERE session_id='S3'").get().c, 4);
  });

  it("rejects a non-export object", () => {
    assert.throws(() => importExportBundle(db, { foo: "bar" }), /recognizable dashboard export/);
    assert.throws(() => importExportBundle(db, { format: "something-else" }), /Unrecognized/);
  });
});
