import { SignJWT, jwtVerify } from 'jose'
import { cookies } from 'next/headers'
import { scrypt, randomBytes, timingSafeEqual } from 'crypto'
import { db } from '@/lib/db'

const SESSION_SECRET = new TextEncoder().encode(
  process.env.SESSION_SECRET || 'reminder-fallback-secret-change-me-in-production',
)

export const COOKIE_NAME = 'rem_session'

// When BYPASS_AUTH=true we treat every request as the first user in the DB.
const BYPASS_AUTH = process.env.BYPASS_AUTH === 'true'

export interface SessionPayload {
  userId: string
  email: string
  name: string
}

let bypassCache: SessionPayload | null = null
async function getBypassSession(): Promise<SessionPayload | null> {
  if (bypassCache) return bypassCache
  const user = await db.user.findFirst({
    orderBy: { createdAt: 'asc' },
    select: { id: true, email: true, name: true },
  })
  if (!user) return null
  bypassCache = { userId: user.id, email: user.email, name: user.name }
  return bypassCache
}

export async function hashPassword(password: string): Promise<string> {
  const salt = randomBytes(16).toString('hex')
  return new Promise((resolve, reject) => {
    scrypt(password, salt, 64, (err, derivedKey) => {
      if (err) return reject(err)
      resolve(`${salt}:${derivedKey.toString('hex')}`)
    })
  })
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  const [salt, key] = hash.split(':')
  if (!salt || !key) return false
  return new Promise((resolve, reject) => {
    scrypt(password, salt, 64, (err, derivedKey) => {
      if (err) return reject(err)
      const stored = Buffer.from(key, 'hex')
      if (stored.length !== derivedKey.length) return resolve(false)
      resolve(timingSafeEqual(stored, derivedKey))
    })
  })
}

// Sessions live as long as browsers allow persistent cookies (~400 days, the
// Chrome cap). The point: log in once, stay logged in. Keep the JWT exp and the
// cookie maxAge in sync.
const SESSION_DAYS = 400

export async function createSessionToken(payload: SessionPayload): Promise<string> {
  return await new SignJWT({ ...payload })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime(`${SESSION_DAYS}d`)
    .sign(SESSION_SECRET)
}

export const SESSION_COOKIE_OPTIONS = {
  httpOnly: true,
  secure: process.env.SECURE_COOKIES === 'true',
  sameSite: 'lax' as const,
  maxAge: 60 * 60 * 24 * SESSION_DAYS,
  path: '/',
}

export async function createSession(payload: SessionPayload) {
  const token = await createSessionToken(payload)
  const cookieStore = await cookies()
  cookieStore.set(COOKIE_NAME, token, SESSION_COOKIE_OPTIONS)
}

export async function getSession(): Promise<SessionPayload | null> {
  if (BYPASS_AUTH) return getBypassSession()
  const cookieStore = await cookies()
  const token = cookieStore.get(COOKIE_NAME)?.value
  if (!token) return null
  try {
    const { payload } = await jwtVerify(token, SESSION_SECRET)
    // A verified token is not automatically a usable one. Every scoped query
    // spreads `{ ownerId: session.userId }` into a Prisma `where`, and Prisma
    // DROPS a key whose value is undefined — so a token without a userId would
    // not fail, it would quietly run unscoped and match another account's row.
    // Minting one needs SESSION_SECRET, so this is not a way in; it is making
    // sure the one shape that silently widens a scope can never reach a query.
    if (typeof payload.userId !== 'string' || !payload.userId) return null
    return payload as unknown as SessionPayload
  } catch {
    return null
  }
}

export async function destroySession() {
  const cookieStore = await cookies()
  cookieStore.delete(COOKIE_NAME)
}

export async function requireSession(): Promise<SessionPayload> {
  const session = await getSession()
  if (!session) throw new Error('Unauthorized')
  return session
}

// Admin is read from the DB on every check, never from the JWT: sessions last
// 400 days, so a token minted today would still claim admin long after the flag
// was taken away. One primary-key lookup is a fair price for that.
export async function isAdmin(userId: string): Promise<boolean> {
  const u = await db.user.findUnique({ where: { id: userId }, select: { isAdmin: true } })
  return !!u?.isAdmin
}

// Signed in AND admin, else null. Gates the household-infrastructure surfaces
// (trading dashboard, host health, agent triggers) that are Eric's alone.
export async function getAdminSession(): Promise<SessionPayload | null> {
  const session = await getSession()
  if (!session) return null
  return (await isAdmin(session.userId)) ? session : null
}
