import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import type { SignedAgentEvent } from "@pons-agent/protocol";
import { AgentState } from "../src/state.js";
import { ExecutionLease } from "../src/lease.js";

const walletA = `0x${"aa".repeat(20)}`;
const walletB = `0x${"bb".repeat(20)}`;

test("ledger balances preserve credits and debits exactly", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-state-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    state.addLedgerEntry(walletA, { id: "credit", bucket: "buyback", direction: "credit", asset: "0xabc", amount: "101" });
    state.addLedgerEntry(walletA, { id: "debit", bucket: "buyback", direction: "debit", asset: "0xAbC", amount: "40" });
    assert.equal(state.getBucketBalance(walletA, "buyback", "0xABC"), 61n);
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("buyback reservations are atomic and cannot exceed the allocated balance", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-reservation-"));
  const path = join(directory, "state.db");
  const first = new AgentState(path);
  const second = new AgentState(path);
  try {
    first.addLedgerEntry(walletA, { id: "allocation", bucket: "buyback", direction: "credit", asset: "0xabc", amount: "100" });
    assert.equal(first.startBuybackExecutionAndReserve(walletA,
      { id: "execution-a", target: "0xtoken", amount: "60" },
      { id: "execution-a:buyback-reservation", asset: "0xabc" },
    ), true);
    assert.equal(second.startBuybackExecutionAndReserve(walletA,
      { id: "execution-b", target: "0xtoken", amount: "50" },
      { id: "execution-b:buyback-reservation", asset: "0xabc" },
    ), false);
    assert.equal(first.getBucketBalance(walletA, "buyback", "0xabc"), 40n);
    assert.equal(second.getExecution(walletA, "execution-b"), undefined);
  } finally {
    first.close();
    second.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("failed reservations release once while uncertain executions remain frozen", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-release-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    state.addLedgerEntry(walletA, { id: "allocation", bucket: "buyback", direction: "credit", asset: "0xabc", amount: "100" });
    assert.equal(state.startBuybackExecutionAndReserve(walletA,
      { id: "execution-uncertain", target: "0xtoken", amount: "60" },
      { id: "execution-uncertain:buyback-reservation", asset: "0xabc" },
    ), true);
    state.updateExecution(walletA, "execution-uncertain", "uncertain", { txHash: `0x${"11".repeat(32)}` });
    assert.equal(state.getBucketBalance(walletA, "buyback", "0xabc"), 40n);

    assert.equal(state.releaseBucketFunds(walletA, "execution-uncertain:buyback-reservation", "buyback", "0xabc", "60"), true);
    assert.equal(state.releaseBucketFunds(walletA, "execution-uncertain:buyback-reservation", "buyback", "0xabc", "60"), false);
    assert.equal(state.releaseBucketFunds(walletA, "missing", "buyback", "0xabc", "60"), false);
    assert.equal(state.getBucketBalance(walletA, "buyback", "0xabc"), 100n);
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("event sequence is monotonic", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-sequence-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    assert.equal(state.nextSequence(), 1);
    assert.equal(state.nextSequence(), 2);
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("runtime lock allows only one active Agent owner", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-lock-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    assert.equal(state.acquireRuntimeLock("agent-runner", "owner-a", 60_000), true);
    assert.equal(state.acquireRuntimeLock("agent-runner", "owner-b", 60_000), false);
    assert.equal(state.renewRuntimeLock("agent-runner", "owner-a", 60_000), true);
    state.releaseRuntimeLock("agent-runner", "owner-a");
    assert.equal(state.acquireRuntimeLock("agent-runner", "owner-b", 60_000), true);
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("execution lease serializes runner and manual wallet commands", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-lease-"));
  const path = join(directory, "state.db");
  const first = new ExecutionLease(path, 60_000);
  const second = new ExecutionLease(path, 60_000);
  try {
    assert.equal(first.acquire(), true);
    assert.equal(second.acquire(), false);
    first.assertActive();
    first.release();
    assert.equal(second.acquire(), true);
  } finally {
    first.release();
    second.release();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("public events remain pending until delivery succeeds", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-outbox-"));
  const state = new AgentState(join(directory, "state.db"));
  const event: SignedAgentEvent = {
    version: 1,
    eventId: "10d8917f-ce44-4fff-9581-ec6d6786d942",
    agentId: "test-agent",
    sequence: 1,
    type: "trend.detected",
    status: "observed",
    occurredAt: "2026-07-22T00:00:00.000Z",
    chainId: 4663,
    payload: { topic: "test" },
    signer: `0x${"11".repeat(20)}`,
    signature: `0x${"22".repeat(65)}`,
  };
  try {
    state.enqueuePublicEvent(event);
    assert.deepEqual(state.getPublicEventOutboxStatus(), { pending: 1, sent: 0 });
    assert.equal(state.listPendingPublicEvents()[0]?.eventId, event.eventId);
    state.markPublicEventFailed(event.eventId, "temporary outage");
    assert.deepEqual(state.getPublicEventOutboxStatus(), { pending: 1, sent: 0 });
    state.markPublicEventSent(event.eventId);
    assert.deepEqual(state.getPublicEventOutboxStatus(), { pending: 0, sent: 1 });
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("cycle history records completed and failed cycles", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-cycles-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    state.startCycle("cycle-complete");
    state.completeCycle("cycle-complete", 3);
    state.startCycle("cycle-failed");
    state.failCycle("cycle-failed", "provider unavailable");
    const cycles = state.listRecentCycles();
    assert.equal(cycles.length, 2);
    assert.equal(cycles.find((cycle) => cycle.id === "cycle-complete")?.decisionCount, 3);
    assert.equal(cycles.find((cycle) => cycle.id === "cycle-failed")?.status, "failed");
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("operator commands are claimed once and retain their result", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-commands-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    state.enqueueOperatorCommand(walletA, { id: "launch-1", kind: "launch", request: { name: "Test", symbol: "TST" } });
    const command = state.claimNextOperatorCommand(walletA);
    assert.equal(command?.id, "launch-1");
    assert.equal(state.claimNextOperatorCommand(walletA), undefined);
    state.completeOperatorCommand(walletA, "launch-1", { mode: "simulated" });
    const stored = state.listOperatorCommands(walletA)[0];
    assert.equal(stored?.status, "completed");
    assert.deepEqual(JSON.parse(stored?.resultJson ?? "{}"), { mode: "simulated" });
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("wallet launch, topic, ledger, execution, and command state is isolated", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-wallet-scope-"));
  const state = new AgentState(join(directory, "state.db"));
  try {
    state.addLaunch(walletA, { token: `0x${"01".repeat(20)}`, pool: `0x${"02".repeat(20)}`, txHash: `0x${"03".repeat(32)}`, name: "Alpha", symbol: "ALPHA" });
    state.recordLaunchedTopic(walletA, "#hot", `0x${"01".repeat(20)}`, `0x${"03".repeat(32)}`);
    state.recordLaunchedTopic(walletB, "#hot", `0x${"04".repeat(20)}`, `0x${"05".repeat(32)}`);
    state.addLedgerEntry(walletA, { id: "wallet-a-credit", bucket: "buyback", direction: "credit", asset: "0xasset", amount: "100" });
    state.startExecution(walletA, { id: "wallet-a-execution", kind: "claim" });
    state.enqueueOperatorCommand(walletA, { id: "wallet-a-command", kind: "claim", request: {} });

    assert.equal(state.listLaunches(walletA).length, 1);
    assert.equal(state.listLaunches(walletB).length, 0);
    assert.equal(state.hasLaunchedTopic(walletA, "#hot"), true);
    assert.equal(state.hasLaunchedTopic(walletB, "#hot"), true);
    assert.equal(state.getBucketBalance(walletB, "buyback", "0xasset"), 0n);
    assert.equal(state.listRecentExecutions(walletB).length, 0);
    assert.equal(state.claimNextOperatorCommand(walletB), undefined);
    assert.equal(state.claimNextOperatorCommand(walletA)?.id, "wallet-a-command");
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("legacy unscoped state is assigned once to the original wallet", () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-wallet-migration-"));
  const path = join(directory, "state.db");
  const state = new AgentState(path);
  try {
    state.database.prepare(`
      INSERT INTO launches(token, wallet_address, pool, tx_hash, name, symbol, created_at)
      VALUES(?, '', ?, ?, 'Legacy', 'LEG', ?)
    `).run(`0x${"10".repeat(20)}`, `0x${"11".repeat(20)}`, `0x${"12".repeat(32)}`, new Date().toISOString());
    state.scopeLegacyWalletData(walletA);
    state.scopeLegacyWalletData(walletB);
    assert.equal(state.listLaunches(walletA).length, 1);
    assert.equal(state.listLaunches(walletB).length, 0);
  } finally {
    state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});
