import { eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { db, schema } from "../../../apps/api/src/db/index.js";
import { buildTestApp, loginAsAdmin, type TestApp } from "../test-server.js";

let testApp: TestApp;
let adminToken: string;

beforeAll(async () => {
  testApp = await buildTestApp();
  adminToken = await loginAsAdmin(testApp.app);
}, 30_000);

afterAll(async () => {
  await testApp.cleanup();
}, 10_000);

describe("API key permission scoping", () => {
  it("creates a key with scoped permissions", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "scoped-key", permissions: ["tools:use", "files:own"] },
    });
    expect(res.statusCode).toBe(201);
    const body = JSON.parse(res.body);
    expect(body.permissions).toEqual(["tools:use", "files:own"]);
    expect(body.key).toBeTruthy();
  });

  it("rejects permissions the user does not have", async () => {
    await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username: "scopetest", password: "ScopeTest1", role: "user" },
    });
    await db
      .update(schema.users)
      .set({ mustChangePassword: false })
      .where(eq(schema.users.username, "scopetest"));

    const loginRes = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: "scopetest", password: "ScopeTest1" },
    });
    const userToken = JSON.parse(loginRes.body).token;

    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${userToken}` },
      payload: { name: "bad-scope", permissions: ["users:manage"] },
    });
    expect(res.statusCode).toBe(400);
  });

  it("scoped API key is restricted to its permissions", async () => {
    const createRes = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "readonly-key", permissions: ["tools:use", "settings:read"] },
    });
    const apiKey = JSON.parse(createRes.body).key;

    const settingsRes = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/settings",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(settingsRes.statusCode).toBe(200);

    const writeRes = await testApp.app.inject({
      method: "PUT",
      url: "/api/v1/settings",
      headers: { authorization: `Bearer ${apiKey}` },
      payload: { testSetting: "hacked" },
    });
    expect(writeRes.statusCode).toBe(403);
  });

  it("null permissions inherits all from user role", async () => {
    const createRes = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "full-key" },
    });
    const body = JSON.parse(createRes.body);
    expect(body.permissions).toBeNull();

    const writeRes = await testApp.app.inject({
      method: "PUT",
      url: "/api/v1/settings",
      headers: { authorization: `Bearer ${body.key}` },
      payload: { testSetting: "value" },
    });
    expect(writeRes.statusCode).toBe(200);
  });

  it("GET /api/v1/api-keys returns permissions field", async () => {
    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
    });
    const body = JSON.parse(res.body);
    expect(body.apiKeys.length).toBeGreaterThan(0);
    const scopedKey = body.apiKeys.find((k: any) => k.name === "scoped-key");
    expect(scopedKey).toBeDefined();
    expect(scopedKey.permissions).toEqual(["tools:use", "files:own"]);
  });
});

describe("API key expiration", () => {
  it("creates key with expiration", async () => {
    const future = new Date(Date.now() + 86400000).toISOString(); // 24h from now
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "expiring-key", expiresAt: future },
    });
    expect(res.statusCode).toBe(201);
    const body = JSON.parse(res.body);
    expect(body.expiresAt).toBeTruthy();
  });

  it("rejects past expiration date", async () => {
    const past = new Date(Date.now() - 86400000).toISOString();
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "past-key", expiresAt: past },
    });
    expect(res.statusCode).toBe(400);
  });

  it("expired key returns 401", async () => {
    // Create a key, then manually set its expiration to the past
    const createRes = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "will-expire", expiresAt: new Date(Date.now() + 86400000).toISOString() },
    });
    const apiKey = JSON.parse(createRes.body).key;
    const keyId = JSON.parse(createRes.body).id;

    // Manually expire the key in DB
    await db
      .update(schema.apiKeys)
      .set({ expiresAt: new Date(Date.now() - 1000) })
      .where(eq(schema.apiKeys.id, keyId));

    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/settings",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(res.statusCode).toBe(401);
  });

  it("GET returns expiresAt field", async () => {
    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${adminToken}` },
    });
    const body = JSON.parse(res.body);
    const expiringKey = body.apiKeys.find((k: any) => k.name === "expiring-key");
    expect(expiringKey).toBeDefined();
    expect(expiringKey.expiresAt).toBeTruthy();
  });
});
