import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
import { Hono } from "hono";
import type { StudioContext } from "../../core/studio-context";
import { CredentialVault } from "../../encryption/credential-vault";
import {
  closeTestPgDatabase,
  connectTestPgDatabase,
  resetTestPgDatabase,
  seedCommonTestPgFixtures,
} from "../../database/test-db-pg";
import type { StudioDatabase } from "../../database";
import { createDownstreamTokenRoutes } from "./downstream-token";

describe("Downstream Token Routes", () => {
  let database: StudioDatabase;
  let app: Hono<{ Variables: { studioContext: StudioContext } }>;

  beforeEach(async () => {
    database = await connectTestPgDatabase();
    await resetTestPgDatabase(database);
    await seedCommonTestPgFixtures(database);

    // Create test connection for FK constraint
    const { sql } = await import("kysely");
    const now = new Date().toISOString();
    await sql`
      INSERT INTO connections (id, organization_id, created_by, title, connection_type, connection_url, status, created_at, updated_at)
      VALUES ('conn_1', 'org_test', 'user_test', 'Test', 'HTTP', 'https://test.com', 'active', ${now}, ${now})
      ON CONFLICT (id) DO NOTHING
    `.execute(database.db);

    const vault = new CredentialVault(CredentialVault.generateKey());

    const ctx = {
      db: database.db,
      vault,
      organization: { id: "org_1" },
      auth: { user: { id: "user_1" } },
      storage: {
        connections: {
          findById: mock(async () => ({ id: "conn_1" })),
        },
      },
    } as unknown as StudioContext;

    app = new Hono();
    app.use("*", async (c, next) => {
      c.set("studioContext", ctx);
      await next();
    });
    app.route("/", createDownstreamTokenRoutes());
  });

  afterEach(async () => {
    await closeTestPgDatabase(database);
    mock.restore();
  });

  it("rejects invalid tokenEndpoint", async () => {
    const res = await app.request("/connections/conn_1/oauth-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        accessToken: "at",
        tokenEndpoint: "not-a-url",
      }),
    });

    expect(res.status).toBe(400);
    const body = (await res.json()) as { error: string };
    expect(body.error).toBe("tokenEndpoint must be a valid URL");
  });

  it("rejects non-http(s) tokenEndpoint", async () => {
    const res = await app.request("/connections/conn_1/oauth-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        accessToken: "at",
        tokenEndpoint: "javascript:alert(1)",
      }),
    });

    expect(res.status).toBe(400);
    const body = (await res.json()) as { error: string };
    expect(body.error).toBe("tokenEndpoint must be an http(s) URL");
  });

  it("accepts http(s) tokenEndpoint", async () => {
    const res = await app.request("/connections/conn_1/oauth-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        accessToken: "at",
        refreshToken: "rt",
        expiresIn: 3600,
        tokenEndpoint: "https://example.com/token",
      }),
    });

    expect(res.status).toBe(200);
    const body = (await res.json()) as { success: boolean; expiresAt: string };
    expect(body.success).toBe(true);
    expect(body.expiresAt).toBeTruthy();
  });
});
