import test from "node:test";
import assert from "node:assert/strict";
import { cleanupNotificationState, dismissSessionNotificationState, mergeNotificationState, persistableNotification, readPersistedNotifications, readPersistedSessionSnapshot, writePersistedNotifications, writePersistedSessionSnapshot } from "./notification-persistence.js";

test("notification persistence stores only browser-safe fields", () => {
  const notification = persistableNotification({
    id: "completed:s:2026-07-08T00:00:00.000Z",
    type: "completed",
    title: "Done",
    detail: "Project",
    conversationTitle: "Actual chat title",
    accountId: "a",
    sessionId: "s",
    createdAt: "2026-07-08T00:00:00.000Z",
    codexHome: "C:\\Users\\secret\\.codex",
    token: "secret",
    rawEvent: { cwd: "C:\\Project\\Codex-remote" }
  });

  assert.deepEqual(notification, {
    id: "completed:s:2026-07-08T00:00:00.000Z",
    type: "completed",
    title: "Done",
    detail: "Project",
    conversationTitle: "Actual chat title",
    accountId: "a",
    sessionId: "s",
    createdAt: "2026-07-08T00:00:00.000Z"
  });
});

test("notification persistence reloads unread notifications and dismissed ids", () => {
  const storage = memoryStorage();

  writePersistedNotifications(storage, {
    notifications: [{
      id: "approval:s:2026-07-08T00:00:00.000Z",
      type: "approval",
      title: "Approval needed",
      detail: "Project",
      accountId: "a",
      sessionId: "s",
      createdAt: "2026-07-08T00:00:00.000Z",
      auth: "secret"
    }],
    dismissedIds: ["completed:s:old", "completed:s:old", ""]
  });

  assert.deepEqual(readPersistedNotifications(storage), {
    notifications: [{
      id: "approval:s:2026-07-08T00:00:00.000Z",
      type: "approval",
      title: "Approval needed",
      detail: "Project",
      accountId: "a",
      sessionId: "s",
      createdAt: "2026-07-08T00:00:00.000Z"
    }],
    dismissedIds: ["completed:s:old"]
  });
});

test("notification persistence ignores invalid saved data", () => {
  const storage = memoryStorage();
  storage.setItem("codexRemote.notifications", JSON.stringify({
    notifications: [{ type: "completed", title: "Done", createdAt: "2026-07-08T00:00:00.000Z" }],
    dismissedIds: [null, "dismissed"]
  }));

  assert.deepEqual(readPersistedNotifications(storage), {
    notifications: [],
    dismissedIds: ["dismissed"]
  });
});

test("notification cleanup expires stale usage, weekly reset, and completed notifications", () => {
  const state = {
    notifications: [
      notification("usage-low:old", "usage-low", "2026-07-08T00:00:00.000Z"),
      notification("weekly-reset:old", "weekly-reset", "2026-07-08T00:00:00.000Z"),
      notification("completed:old", "completed", "2026-07-01T00:00:00.000Z"),
      notification("completed:fresh", "completed", "2026-07-02T01:00:00.000Z"),
      notification("approval:active", "approval", "2026-06-01T00:00:00.000Z", "active")
    ],
    dismissedIds: ["usage-low:old"]
  };

  assert.deepEqual(cleanupNotificationState(state, {
    sessions: [{ id: "active", status: "waiting_approval" }],
    now: "2026-07-09T00:00:00.000Z"
  }), {
    notifications: [
      notification("completed:fresh", "completed", "2026-07-02T01:00:00.000Z"),
      notification("approval:active", "approval", "2026-06-01T00:00:00.000Z", "active")
    ],
    dismissedIds: ["usage-low:old"]
  });
});

test("notification cleanup removes approval notifications after the approval state resolves", () => {
  const state = {
    notifications: [
      notification("approval:resolved", "approval", "2026-07-08T00:00:00.000Z", "resolved"),
      notification("approval:missing", "approval", "2026-07-08T00:00:00.000Z", "missing")
    ],
    dismissedIds: []
  };

  assert.deepEqual(cleanupNotificationState(state, {
    sessions: [{ id: "resolved", status: "running" }],
    now: "2026-07-09T00:00:00.000Z"
  }), {
    notifications: [
      notification("approval:missing", "approval", "2026-07-08T00:00:00.000Z", "missing")
    ],
    dismissedIds: []
  });
});

test("notification cleanup fills a missing alert title from the matching session", () => {
  const alert = notification("completed:s", "completed", "2026-07-08T00:00:00.000Z", "s");

  assert.deepEqual(cleanupNotificationState({ notifications: [alert], dismissedIds: [] }, {
    sessions: [{ id: "s", status: "completed", conversationTitle: "Recovered chat title" }],
    now: "2026-07-08T01:00:00.000Z"
  }), {
    notifications: [{ ...alert, conversationTitle: "Recovered chat title" }],
    dismissedIds: []
  });
});

test("notification merge keeps only the latest unread alert for the same session", () => {
  const current = {
    notifications: [
      notification("completed:s:old", "completed", "2026-07-08T00:00:00.000Z", "s"),
      notification("approval:other", "approval", "2026-07-08T00:01:00.000Z", "other")
    ],
    dismissedIds: ["completed:s:dismissed"]
  };
  const latest = notification("completed:s:new", "completed", "2026-07-08T00:02:00.000Z", "s");

  assert.deepEqual(mergeNotificationState(current, [latest]), {
    notifications: [
      latest,
      notification("approval:other", "approval", "2026-07-08T00:01:00.000Z", "other")
    ],
    dismissedIds: ["completed:s:dismissed"]
  });
});

test("notification cleanup collapses legacy duplicate session alerts", () => {
  const latest = notification("completed:s:new", "completed", "2026-07-08T00:02:00.000Z", "s");

  assert.deepEqual(cleanupNotificationState({
    notifications: [
      notification("completed:s:old", "completed", "2026-07-08T00:00:00.000Z", "s"),
      latest,
      notification("usage-low:a", "usage-low", "2026-07-08T00:01:00.000Z")
    ],
    dismissedIds: []
  }, { now: "2026-07-08T01:00:00.000Z" }).notifications, [
    latest,
    notification("usage-low:a", "usage-low", "2026-07-08T00:01:00.000Z")
  ]);
});

test("dismissing a session clears every legacy alert for that session", () => {
  const other = notification("completed:other", "completed", "2026-07-08T00:02:00.000Z", "other");

  assert.deepEqual(dismissSessionNotificationState({
    notifications: [
      notification("completed:s:new", "completed", "2026-07-08T00:02:00.000Z", "s"),
      notification("completed:s:old", "completed", "2026-07-08T00:00:00.000Z", "s"),
      other
    ],
    dismissedIds: ["already-dismissed"]
  }, "s"), {
    notifications: [other],
    dismissedIds: ["completed:s:new", "completed:s:old", "already-dismissed"]
  });
});

test("dismissing completed session alerts preserves alerts that still need attention", () => {
  const approval = notification("approval:s", "approval", "2026-07-08T00:03:00.000Z", "s");
  const other = notification("completed:other", "completed", "2026-07-08T00:02:00.000Z", "other");

  assert.deepEqual(dismissSessionNotificationState({
    notifications: [
      approval,
      notification("completed:s", "completed", "2026-07-08T00:02:00.000Z", "s"),
      other
    ],
    dismissedIds: ["already-dismissed"]
  }, "s", { types: ["completed"] }), {
    notifications: [approval, other],
    dismissedIds: ["completed:s", "already-dismissed"]
  });
});

test("notification persistence restores only safe session observations", () => {
  const storage = memoryStorage();
  writePersistedSessionSnapshot(storage, [{
    id: "session-1",
    accountId: "account-1",
    projectLabel: "Project",
    codexThreadId: "thread-1",
    conversationTitle: "Actual chat title",
    status: "running",
    updatedAt: "2026-07-08T00:00:00.000Z",
    codexHome: "C:\\Users\\secret\\.codex"
  }]);

  assert.deepEqual(readPersistedSessionSnapshot(storage), [{
    id: "session-1",
    accountId: "account-1",
    projectLabel: "Project",
    codexThreadId: "thread-1",
    conversationTitle: "Actual chat title",
    status: "running",
    updatedAt: "2026-07-08T00:00:00.000Z"
  }]);
});

test("notification persistence bounds dismissed ids and tolerates storage failures", () => {
  const state = cleanupNotificationState({
    notifications: [],
    dismissedIds: Array.from({ length: 600 }, (_, index) => `dismissed-${index}`)
  });
  assert.equal(state.dismissedIds.length, 500);
  assert.doesNotThrow(() => writePersistedNotifications({ setItem() { throw new Error("quota"); } }, state));
  assert.doesNotThrow(() => writePersistedSessionSnapshot({ setItem() { throw new Error("quota"); } }, []));
});

function memoryStorage() {
  const values = new Map();
  return {
    getItem: (key) => values.get(key) ?? null,
    setItem: (key, value) => values.set(key, String(value)),
    removeItem: (key) => values.delete(key)
  };
}

function notification(id, type, createdAt, sessionId = null) {
  return {
    id,
    type,
    title: type,
    detail: "Project",
    accountId: "a",
    sessionId,
    createdAt
  };
}
