import { createHash, randomBytes } from 'node:crypto'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'
import { z } from 'zod'
import { env } from '../config/env.js'
import { unauthorized } from './errors.js'

const identitySchema = z.object({
  sub: z.string().min(1),
  kind: z.enum(['user', 'guest']),
  name: z.string().min(1),
  email: z.string().email().optional(),
})

export type Identity = z.infer<typeof identitySchema>

export const hashPassword = (password: string) => bcrypt.hash(password, 12)

export const verifyPassword = (password: string, hash: string) => bcrypt.compare(password, hash)

export const hashToken = (token: string) => createHash('sha256').update(token).digest('hex')

export const newId = () => randomBytes(16).toString('hex')

export const signAccessToken = (identity: Identity) =>
  jwt.sign(identity, env.JWT_ACCESS_SECRET, { expiresIn: env.ACCESS_TOKEN_TTL as jwt.SignOptions['expiresIn'] })

export const signRefreshToken = (identity: Identity) =>
  jwt.sign({ sub: identity.sub, kind: identity.kind }, env.JWT_REFRESH_SECRET, {
    expiresIn: env.REFRESH_TOKEN_TTL as jwt.SignOptions['expiresIn'],
  })

export const readAccessToken = (token: string): Identity => {
  try {
    const payload = jwt.verify(token, env.JWT_ACCESS_SECRET)
    const parsed = identitySchema.safeParse(payload)
    if (!parsed.success) throw unauthorized('Invalid access token')
    return parsed.data
  } catch (error) {
    if (error instanceof jwt.JsonWebTokenError || error instanceof jwt.TokenExpiredError) {
      throw unauthorized('Invalid access token')
    }
    throw error
  }
}

export const readRefreshToken = (token: string): { sub: string; kind: 'user' | 'guest' } => {
  try {
    const payload = jwt.verify(token, env.JWT_REFRESH_SECRET)
    const parsed = z.object({ sub: z.string(), kind: z.enum(['user', 'guest']) }).safeParse(payload)
    if (!parsed.success) throw unauthorized('Invalid refresh token')
    return parsed.data
  } catch (error) {
    if (error instanceof jwt.JsonWebTokenError || error instanceof jwt.TokenExpiredError) {
      throw unauthorized('Invalid refresh token')
    }
    throw error
  }
}
