const assert = require("assert");
const fs = require("fs");
const path = require("path");

const root = path.join(__dirname, "..");
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-command-policies-"));
const serviceDir = path.join(sandbox, "src", "services");
let database = null;

(async () => {
try {
  fs.cpSync(path.join(root, "src", "services"), serviceDir, { recursive: true });
  database = require(path.join(serviceDir, "db.js"));
  database.migrate();
  const conditional = require(path.join(serviceDir, "command-conditional.js"));
  const policies = require(path.join(serviceDir, "command-policies.js"));
  const defaultGroup = policies.listGroups().find((group) => group.is_default);
  assert(defaultGroup, "a protected default command group must be created automatically");
  assert.throws(() => policies.deleteGroup(defaultGroup.id), /cannot be deleted/);

  const replies = conditional.normalizeConditionalReplies([
    { keyword: "backseating", response: "Please avoid backseating." },
    { keyword: "spoilers", response: "No spoilers." }
  ]);
  assert.strictEqual(conditional.findConditionalReply(replies, "backseating").match.response, "Please avoid backseating.");
  const fuzzy = conditional.findConditionalReply(replies, "backseatng");
  assert.strictEqual(fuzzy.match.keyword, "backseating");
  assert.strictEqual(fuzzy.fuzzy, true);
  assert.strictEqual(conditional.findConditionalReply(replies, "something unrelated").match, null);
  const aliases = conditional.conditionalRepliesFromBody({
    conditional_keyword: "backseat, backseating, hints",
    conditional_response: "Please ask before helping."
  });
  assert.strictEqual(aliases.ok, true);
  assert.strictEqual(aliases.replies[0].keyword, "backseat, backseating, hints");
  assert.strictEqual(conditional.normalizeConditionalReplyRows(JSON.stringify(aliases.replies))[0].keyword, "backseat, backseating, hints");
  for (const alias of ["backseat", "backseating", "hints"]) {
    assert.strictEqual(conditional.findConditionalReply(aliases.replies, alias).match.response, "Please ask before helping.");
  }
  const duplicateAlias = conditional.conditionalRepliesFromBody({
    conditional_keyword: ["spoilers, leaks", "leaks"],
    conditional_response: ["First", "Second"]
  });
  assert.strictEqual(duplicateAlias.ok, false);
  assert.match(duplicateAlias.errors[0], /leaks.*more than once/);

  const groupId = policies.createGroup({
    name: "Verification group",
    policy: {
      window: { uses: 3, seconds: 30, scope: "global" },
      requirement: { role: "subscriber", subscriberTier: 2 },
      cost: { amount: 5 }
    }
  });
  const key = policies.commandKey("verification", "hello");
  policies.saveCommandPolicy(key, {
    window: { uses: 1, seconds: 60, scope: "user" },
    requirement: false,
    cost: { amount: 2 }
  }, [groupId]);
  const resolved = policies.resolvePolicy(key);
  assert.strictEqual(resolved.windows.length, 1, "a direct rate limit must replace group limits");
  assert.strictEqual(resolved.windows[0].value.uses, 1);
  assert.strictEqual(resolved.requirements.length, 0, "a direct public setting must replace group roles");
  assert.strictEqual(resolved.cost, 2, "a direct cost must replace the group cost");
  policies.updateGroup(defaultGroup.id, {
    name: defaultGroup.name,
    description: defaultGroup.description,
    policy: { window: { uses: 4, seconds: 90, scope: "global" } }
  });
  const inheritedKey = policies.commandKey("verification", "inherited");
  assert.strictEqual(policies.resolvePolicy(inheritedKey).windows[0].value.uses, 4, "the default group must apply without explicit membership");
  policies.saveCommandPolicies([
    { commandKey: inheritedKey, policy: { cost: { amount: 3 } }, groupIds: [] },
    { commandKey: policies.commandKey("verification", "second"), policy: { requirement: false }, groupIds: [groupId] }
  ]);
  assert.strictEqual(policies.resolvePolicy(inheritedKey).cost, 3, "batch saves must persist every submitted command");

  const charges = [];
  global.lumiFrameworks = {
    economy: {
      removeBalance(payload) { charges.push(["charge", payload]); return { ok: true }; },
      addBalance(payload) { charges.push(["refund", payload]); return "refund-id"; }
    }
  };
  const ctx = {
    platform: "discord",
    trigger: "hello",
    raw: "!hello",
    user: { id: "verification-user", platformId: "platform-user" },
    platformUser: { id: "platform-user" },
    meta: {}
  };
  const first = await policies.beginCommandUse({ commandKey: key, ctx });
  assert.strictEqual(first.allowed, true);
  assert.strictEqual(charges[0][0], "charge");
  const limited = await policies.beginCommandUse({ commandKey: key, ctx });
  assert.strictEqual(limited.allowed, false, "the second use inside the window must be denied");
  await first.rollback("verification");
  assert.strictEqual(charges[1][0], "refund", "failed commands must refund their logged currency cost");
  const afterRollback = await policies.beginCommandUse({ commandKey: key, ctx });
  assert.strictEqual(afterRollback.allowed, true, "rollback must release the rate-limit reservation");
  await afterRollback.commit();

  const dbColumns = database.db.prepare("PRAGMA table_info(custom_commands)").all().map((column) => column.name);
  assert(dbColumns.includes("conditional_replies_json"));
  assert(dbColumns.includes("conditional_fuzzy"));
  assert(database.db.prepare("PRAGMA table_info(command_groups)").all().some((column) => column.name === "is_default"));
  for (const table of ["command_groups", "command_group_members", "command_policies", "command_policy_uses", "command_stream_sessions"]) {
    assert(database.db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table), `missing ${table}`);
  }

  const server = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
  const view = fs.readFileSync(path.join(root, "src", "web", "views", "admin-command-policies.ejs"), "utf8");
  const commandView = fs.readFileSync(path.join(root, "src", "web", "views", "admin-commands.ejs"), "utf8");
  assert(server.includes('/admin/command-policies') && server.includes('commandPolicyFromBody'));
  assert(view.includes("Command groups") && view.includes("Save all command settings"));
  assert(view.includes("data-policy-search") && view.includes("data-policy-setting-filter"));
  assert(view.includes("Protected default") && view.includes("data-command-policy-batch"));
  assert(commandView.includes('value="conditional"') && commandView.includes("data-conditional-replies"));
  assert(commandView.includes("Separate aliases with commas"));

  database.db.close();
  database = null;
  console.log("Command policies and conditional commands verified.");
} finally {
  try { database?.db?.close?.(); } catch {}
  fs.rmSync(sandbox, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
}
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
