/**
 * logActivity() — the activity_feed writer wired into push/PR/merge paths
 * on 2026-07-15 (see src/hooks/post-receive.ts, src/routes/pulls.tsx,
 * src/lib/pr-merge.ts, src/lib/mcp-tools.ts). Before this, activity_feed
 * was only ever written by fork/use-template/claude-connect/reviewer-suggest,
 * so dashboards and the repo-page "recent push" indicator were blind to
 * anything done through git push, the web PR flow, or the MCP write tools.
 *
 * Uses the same mock.module("../db", ...) fake-db pattern as push.test.ts —
 * `logActivity` must never throw (mirrors `audit()`'s contract), and it
 * must write the exact shape callers rely on (action, targetType, targetId,
 * metadata as a JSON string).
 */

import { describe, it, expect, beforeEach, afterAll, mock } from "bun:test";

const _real_db = await import("../db");

const _inserted: Array<{ table: string; values: any }> = [];
let _shouldThrow = false;

const tableName = (t: any): string => {
  if (!t || typeof t !== "object") return "?";
  if ("action" in t && "repositoryId" in t && "targetType" in t) return "activity_feed";
  return "?";
};

const _fakeDb = {
  db: {
    insert: (t: any) => ({
      values: (vals: any) => {
        if (_shouldThrow) return Promise.reject(new Error("db unavailable"));
        _inserted.push({ table: tableName(t), values: vals });
        return Promise.resolve();
      },
    }),
  },
  getDb: () => _fakeDb.db,
};

mock.module("../db", () => ({ ..._real_db, ..._fakeDb }));

const { logActivity } = await import("../lib/notify");

afterAll(() => {
  mock.module("../db", () => _real_db);
});

beforeEach(() => {
  _inserted.length = 0;
  _shouldThrow = false;
});

describe("logActivity", () => {
  it("writes an activity_feed row with the given action/target/metadata", async () => {
    await logActivity({
      repositoryId: "repo-1",
      userId: "user-1",
      action: "push",
      targetType: "commit",
      targetId: "a".repeat(40),
      metadata: { branch: "main" },
    });

    expect(_inserted).toHaveLength(1);
    expect(_inserted[0]!.table).toBe("activity_feed");
    expect(_inserted[0]!.values).toMatchObject({
      repositoryId: "repo-1",
      userId: "user-1",
      action: "push",
      targetType: "commit",
      targetId: "a".repeat(40),
    });
    expect(_inserted[0]!.values.metadata).toBe(JSON.stringify({ branch: "main" }));
  });

  it("defaults userId to null when omitted", async () => {
    await logActivity({ repositoryId: "repo-1", action: "pr_open" });
    expect(_inserted[0]!.values.userId).toBeNull();
  });

  it("never throws when the DB insert fails — same fail-open contract as audit()", async () => {
    _shouldThrow = true;
    await expect(
      logActivity({ repositoryId: "repo-1", action: "pr_merge" })
    ).resolves.toBeUndefined();
    expect(_inserted).toHaveLength(0);
  });

  it("sets metadata to null when not provided", async () => {
    await logActivity({ repositoryId: "repo-1", action: "comment", targetType: "pull_request", targetId: "42" });
    expect(_inserted[0]!.values.metadata).toBeNull();
  });
});
