import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import Database from "better-sqlite3";
import type { AppDatabase } from "../src/db/database.js";
import { migrate } from "../src/db/migrate.js";
import { createSessionRepository, createMessageRepository } from "../src/sessions/repository.js";
import { createWebhookAuditRepository } from "../src/webhooks/audit.js";
import { runWebhookAuditPurge } from "../src/webhooks/audit.js";

let db: AppDatabase;
let dbPath: string;

beforeEach(() => {
  const dir = join(tmpdir(), `sic-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
  mkdirSync(dir, { recursive: true });
  dbPath = join(dir, "test.db");
  db = new Database(dbPath);
  db.pragma("foreign_keys = ON");
  migrate(db);
});

afterEach(() => {
  db.close();
  if (existsSync(dbPath)) rmSync(dbPath, { force: true });
});

describe("session isolation", () => {
  it("never returns sessions/messages from another user", () => {
    const sessions = createSessionRepository(db);
    const messages = createMessageRepository(db);

    const a = sessions.create("user-a", "A session");
    const b = sessions.create("user-b", "B session");

    messages.create({ sessionId: a.id, userId: "user-a", role: "user", content: "a msg" });
    messages.create({ sessionId: b.id, userId: "user-b", role: "user", content: "b msg" });

    // List filter
    expect(sessions.list("user-a").map((s) => s.id)).toEqual([a.id]);
    expect(sessions.list("user-b").map((s) => s.id)).toEqual([b.id]);

    // get() requires matching user_id
    expect(sessions.get("user-a", b.id)).toBeFalsy();
    expect(sessions.get("user-b", a.id)).toBeFalsy();

    // Messages filter by both session_id and user_id
    const bMessages = messages.listForSession("user-a", b.id);
    expect(bMessages).toEqual([]);
    const aMessages = messages.listForSession("user-a", a.id);
    expect(aMessages).toHaveLength(1);
    expect(aMessages[0]?.content).toBe("a msg");
  });

  it("delete cascades to messages", () => {
    const sessions = createSessionRepository(db);
    const messages = createMessageRepository(db);

    const s = sessions.create("user-a", null);
    const m = messages.create({
      sessionId: s.id,
      userId: "user-a",
      role: "user",
      content: "will be cascaded",
    });

    sessions.delete("user-a", s.id);
    expect(messages.listForSession("user-a", s.id)).toEqual([]);
    // Direct DB check that the message row is gone (not just hidden)
    const row = db.prepare("SELECT id FROM chat_messages WHERE id = ?").get(m.id);
    expect(row).toBeUndefined();
  });

  it("updateTitle only affects the owner's session", () => {
    const sessions = createSessionRepository(db);
    const a = sessions.create("user-a", "Old");
    sessions.updateTitle("user-a", a.id, "New");
    expect(sessions.get("user-a", a.id)?.title).toBe("New");
  });
});

describe("webhook audit + retention", () => {
  it("usageForUserSince aggregates per webhook", () => {
    const sessions = createSessionRepository(db);
    const audit = createWebhookAuditRepository(db);
    const s1 = sessions.create("user-a", "test");
    const s2 = sessions.create("user-b", "test");
    const now = Date.now();
    const fresh = new Date(now - 1_000).toISOString();
    const old = new Date(now - 100 * 86_400_000).toISOString();

    audit.create({ webhookId: "dns-flush", userId: "user-a", sessionId: s1.id, status: "success", createdAt: fresh });
    audit.create({ webhookId: "dns-flush", userId: "user-a", sessionId: s1.id, status: "success", createdAt: fresh });
    audit.create({ webhookId: "dns-flush", userId: "user-a", sessionId: s1.id, status: "error", createdAt: fresh });
    audit.create({ webhookId: "dns-flush", userId: "user-a", sessionId: s1.id, status: "success", createdAt: old });
    audit.create({ webhookId: "other-hook", userId: "user-b", sessionId: s2.id, status: "success", createdAt: fresh });

    const since = new Date(now - 7 * 86_400_000).toISOString();
    const usage = audit.usageForUserSince(since, "user-a");
    expect(usage["dns-flush"]?.runs).toBe(3);
    expect(usage["dns-flush"]?.successes).toBe(2);
    expect(usage["dns-flush"]?.successRate).toBeCloseTo(2 / 3);
    expect(usage["other-hook"]).toBeUndefined();
  });

  it("retention purge deletes old rows but keeps recent ones", () => {
    const sessions = createSessionRepository(db);
    const audit = createWebhookAuditRepository(db);
    const s = sessions.create("user-a", "test");
    const now = Date.now();
    const fresh = new Date(now - 60_000).toISOString();
    const stale = new Date(now - 100 * 86_400_000).toISOString();
    audit.create({ webhookId: "w", userId: "user-a", sessionId: s.id, status: "success", createdAt: fresh });
    audit.create({ webhookId: "w", userId: "user-a", sessionId: s.id, status: "success", createdAt: stale });
    audit.create({ webhookId: "w", userId: "user-a", sessionId: s.id, status: "success", createdAt: stale });

    const report = runWebhookAuditPurge(db, { retentionDays: 30, maxPerUser: 0 });
    expect(report.deletedByAge).toBe(2);

    const remaining = db.prepare("SELECT COUNT(*) as n FROM webhook_runs").get() as { n: number };
    expect(remaining.n).toBe(1);
  });

  it("per-user cap keeps the most recent N", () => {
    const sessions = createSessionRepository(db);
    const audit = createWebhookAuditRepository(db);
    const s = sessions.create("user-a", "test");
    const now = Date.now();
    for (let i = 0; i < 8; i++) {
      const ts = new Date(now - i * 1000).toISOString();
      audit.create({ webhookId: "w", userId: "user-a", sessionId: s.id, status: "success", createdAt: ts });
    }
    const report = runWebhookAuditPurge(db, { retentionDays: 0, maxPerUser: 3 });
    expect(report.deletedByCap).toBe(5);
    const remaining = db.prepare("SELECT COUNT(*) as n FROM webhook_runs").get() as { n: number };
    expect(remaining.n).toBe(3);
  });

  it("listForSession enforces user_id", () => {
    const sessions = createSessionRepository(db);
    const audit = createWebhookAuditRepository(db);
    const sa = sessions.create("user-a", "test");
    const sb = sessions.create("user-b", "test");
    audit.create({ webhookId: "w", userId: "user-a", sessionId: sa.id, status: "success" });
    audit.create({ webhookId: "w", userId: "user-b", sessionId: sb.id, status: "success" });
    expect(audit.listForSession("user-a", sa.id)).toHaveLength(1);
    expect(audit.listForSession("user-b", sb.id)).toHaveLength(1);
    expect(audit.listForSession("user-a", sa.id)[0]?.user_id).toBe("user-a");
  });
});
