import { createHash, randomBytes } from "node:crypto"
import jwt from "jsonwebtoken"
import { FastifyInstance } from "fastify"
import { and, eq, gt, isNull } from "drizzle-orm"

import { authRefreshTokens, authUsers } from "../../db/schema"
import { secrets } from "./secrets"

const ACCESS_TOKEN_TTL_SECONDS = 60 * 60 * 6
const REFRESH_TOKEN_TTL_DAYS = 90

export function createAccessToken(user: { id: string; email: string }, tenantId: number | null) {
    return jwt.sign(
        {
            user_id: user.id,
            email: user.email,
            tenant_id: tenantId,
        },
        secrets.JWT_SECRET!,
        { expiresIn: ACCESS_TOKEN_TTL_SECONDS }
    )
}

export function hashRefreshToken(token: string) {
    return createHash("sha256").update(token, "utf8").digest("hex")
}

export async function issueRefreshToken(
    server: FastifyInstance,
    userId: string,
    tenantId: number | null
) {
    const token = randomBytes(48).toString("base64url")
    const expiresAt = new Date(Date.now() + REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000)

    await server.db.insert(authRefreshTokens).values({
        userId,
        tenantId,
        tokenHash: hashRefreshToken(token),
        expiresAt,
    })

    return token
}

export async function rotateRefreshToken(
    server: FastifyInstance,
    token: string,
    tenantOverride?: number | null,
    expectedUserId?: string
) {
    const tokenHash = hashRefreshToken(token)
    const [session] = await server.db
        .update(authRefreshTokens)
        .set({ revokedAt: new Date() })
        .where(and(
            eq(authRefreshTokens.tokenHash, tokenHash),
            isNull(authRefreshTokens.revokedAt),
            gt(authRefreshTokens.expiresAt, new Date())
        ))
        .returning({
            id: authRefreshTokens.id,
            userId: authRefreshTokens.userId,
            tenantId: authRefreshTokens.tenantId,
        })

    if (!session) return null
    if (expectedUserId && session.userId !== expectedUserId) return null

    const [user] = await server.db
        .select({ id: authUsers.id, email: authUsers.email })
        .from(authUsers)
        .where(eq(authUsers.id, session.userId))
        .limit(1)

    if (!user) return null

    const tenantId = tenantOverride === undefined ? session.tenantId : tenantOverride
    const refreshToken = await issueRefreshToken(server, session.userId, tenantId)
    const accessToken = createAccessToken(user, tenantId)

    return { token: accessToken, refreshToken }
}

export async function revokeRefreshToken(server: FastifyInstance, token: string) {
    await server.db
        .update(authRefreshTokens)
        .set({ revokedAt: new Date() })
        .where(and(
            eq(authRefreshTokens.tokenHash, hashRefreshToken(token)),
            isNull(authRefreshTokens.revokedAt)
        ))
}
