import { z } from "zod"
import { fn } from "./util/fn"
import { Actor } from "./actor"
import { and, Database, eq, isNull, sql } from "./drizzle"
import { Identifier } from "./identifier"
import { KeyTable } from "./schema/key.sql"
import { UserTable } from "./schema/user.sql"
import { AuthTable } from "./schema/auth.sql"

export namespace Key {
  export const list = fn(z.void(), async () => {
    const keys = await Database.use((tx) =>
      tx
        .select({
          id: KeyTable.id,
          name: KeyTable.name,
          key: KeyTable.key,
          timeUsed: KeyTable.timeUsed,
          userID: KeyTable.userID,
          email: AuthTable.subject,
        })
        .from(KeyTable)
        .innerJoin(UserTable, and(eq(KeyTable.userID, UserTable.id), eq(KeyTable.workspaceID, UserTable.workspaceID)))
        .innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
        .where(
          and(
            eq(KeyTable.workspaceID, Actor.workspace()),
            isNull(KeyTable.timeDeleted),
            ...(Actor.userRole() === "admin" ? [] : [eq(KeyTable.userID, Actor.userID())]),
          ),
        )
        .orderBy(sql`${KeyTable.name} DESC`),
    )
    // only return value for user's keys
    return keys.map((key) => ({
      ...key,
      key: key.userID === Actor.userID() ? key.key : undefined,
      keyDisplay: `${key.key.slice(0, 7)}...${key.key.slice(-4)}`,
    }))
  })

  export const create = fn(
    z.object({
      userID: z.string(),
      name: z.string().min(1).max(255),
    }),
    async (input) => {
      const { name } = input

      // Generate secret key: sk- + 64 random characters (upper, lower, numbers)
      const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
      let secretKey = "sk-"
      const array = new Uint32Array(64)
      crypto.getRandomValues(array)
      for (let i = 0, l = array.length; i < l; i++) {
        secretKey += chars[array[i] % chars.length]
      }
      const keyID = Identifier.create("key")

      await Database.use((tx) =>
        tx.insert(KeyTable).values({
          id: keyID,
          workspaceID: Actor.workspace(),
          userID: input.userID,
          name,
          key: secretKey,
          timeUsed: null,
        }),
      )

      return keyID
    },
  )

  export const remove = fn(z.object({ id: z.string() }), async (input) => {
    // only admin can remove other user's keys
    await Database.use((tx) =>
      tx
        .update(KeyTable)
        .set({
          timeDeleted: sql`now()`,
        })
        .where(
          and(
            eq(KeyTable.id, input.id),
            eq(KeyTable.workspaceID, Actor.workspace()),
            ...(Actor.userRole() === "admin" ? [] : [eq(KeyTable.userID, Actor.userID())]),
          ),
        ),
    )
  })
}
