import {
  afterAll,
  beforeAll,
  beforeEach,
  describe,
  expect,
  it,
  vi,
} from "vitest";
import {
  closeDatabase,
  getDatabase,
  initializeDatabase,
} from "../../src/database/drizzle";
import { captureMessage } from "../../src/moderation/messageCapture";
import type { ModerationBroadcaster } from "../../src/moderation/types";

const queueMessageAnalysis = vi.fn();

type TestMessage = Parameters<typeof captureMessage>[0];
type ModerationTestGlobal = typeof globalThis & {
  moderationBroadcaster?: Partial<ModerationBroadcaster>;
};

interface TestDatabase {
  run(sql: string): void;
}

function getTestDatabase(): TestDatabase {
  return getDatabase() as unknown as TestDatabase;
}

vi.mock("../../src/moderation/aiAnalyzer", () => ({
  queueMessageAnalysis: (id: string) => queueMessageAnalysis(id),
}));

function createMessage(id = "message-1"): TestMessage {
  return {
    id,
    guildId: "guild-1",
    channelId: "channel-1",
    author: {
      id: "user-1",
      username: "alice",
      bot: false,
      avatarURL: () => null,
    },
    content: "hello",
    cleanContent: "hello",
    createdTimestamp: 1_700_000_000_000,
    attachments: new Map(),
    stickers: new Map(),
    embeds: [],
    member: null,
    reference: null,
    channel: {
      id: "channel-1",
      name: "general",
      isThread: () => false,
    },
  } as unknown as TestMessage;
}

async function createTables() {
  const db = getTestDatabase();
  db.run(`
    CREATE TABLE IF NOT EXISTS "messages" (
      "id" text PRIMARY KEY NOT NULL,
      "guild_id" text NOT NULL,
      "channel_id" text NOT NULL,
      "thread_id" text,
      "user_id" text NOT NULL,
      "username" text NOT NULL,
      "avatar_url" text,
      "content" text NOT NULL,
      "edited_content" text,
      "created_at" integer NOT NULL,
      "edited_at" integer,
      "deleted_at" integer,
      "type" text DEFAULT 'text' NOT NULL,
      "metadata" text,
      "ai_status" text DEFAULT 'pending' NOT NULL,
      "ai_moderation_flags" text,
      "ai_moderation_score" real,
      "ai_moderation_raw" text,
      "ai_analysis" text,
      "ai_analyzed_at" integer,
      "ai_error" text
    )
  `);
  db.run(`
    CREATE TABLE IF NOT EXISTS "attachments" (
      "id" text PRIMARY KEY NOT NULL,
      "message_id" text NOT NULL,
      "guild_id" text NOT NULL,
      "channel_id" text NOT NULL,
      "thread_id" text,
      "user_id" text NOT NULL,
      "filename" text NOT NULL,
      "size" integer NOT NULL,
      "type" text NOT NULL,
      "discord_url" text NOT NULL,
      "uploaded_url" text,
      "upload_status" text DEFAULT 'pending' NOT NULL,
      "upload_error" text,
      "created_at" integer NOT NULL,
      "uploaded_at" integer
    )
  `);
}

describe("captureMessage", () => {
  beforeAll(async () => {
    await initializeDatabase();
    await createTables();
  });

  beforeEach(async () => {
    queueMessageAnalysis.mockClear();
    const db = getTestDatabase();
    db.run(`DELETE FROM "attachments"`);
    db.run(`DELETE FROM "messages"`);
    delete (globalThis as ModerationTestGlobal).moderationBroadcaster;
  });

  afterAll(async () => {
    await closeDatabase();
  });

  it("does not requeue or rebroadcast a duplicate captured message", async () => {
    const message = createMessage();
    const messageCreated = vi.fn();
    (globalThis as ModerationTestGlobal).moderationBroadcaster = {
      messageCreated,
    };

    await captureMessage(message, "text");
    await captureMessage(message, "text");

    expect(queueMessageAnalysis).toHaveBeenCalledTimes(1);
    expect(messageCreated).toHaveBeenCalledTimes(1);
  });

  it("does not queue or broadcast backlog captures one message at a time", async () => {
    const message = createMessage("backlog-message-1");
    const messageCreated = vi.fn();
    (globalThis as ModerationTestGlobal).moderationBroadcaster = {
      messageCreated,
    };

    await captureMessage(message, "text", { source: "backlog" });

    expect(queueMessageAnalysis).not.toHaveBeenCalled();
    expect(messageCreated).not.toHaveBeenCalled();
  });
});
