import { safeCompare } from '@sim/security/compare'
import { sha256Hex } from '@sim/security/hash'
import { hmacSha256Hex } from '@sim/security/hmac'
import type { NextResponse } from 'next/server'
import { env } from '@/lib/core/config/env'
import { isDev } from '@/lib/core/config/env-flags'

/**
 * Shared authentication utilities for deployed chat endpoints.
 * Handles token generation, validation, and auth cookies. CORS for these
 * endpoints lives in proxy.ts as the single source of truth.
 */

function signPayload(payload: string): string {
  return hmacSha256Hex(payload, env.BETTER_AUTH_SECRET)
}

function passwordSlot(encryptedPassword?: string | null): string {
  if (!encryptedPassword) return ''
  return sha256Hex(encryptedPassword).slice(0, 8)
}

function generateAuthToken(
  deploymentId: string,
  type: string,
  encryptedPassword?: string | null
): string {
  const payload = `${deploymentId}:${type}:${Date.now()}:${passwordSlot(encryptedPassword)}`
  const sig = signPayload(payload)
  return Buffer.from(`${payload}:${sig}`).toString('base64')
}

/**
 * Validates an HMAC-signed authentication token for a chat deployment.
 * Includes a password-derived slot so changing the deployment password immediately
 * invalidates existing sessions.
 */
export function validateAuthToken(
  token: string,
  deploymentId: string,
  authType: string,
  encryptedPassword?: string | null
): boolean {
  try {
    const decoded = Buffer.from(token, 'base64').toString()
    const lastColon = decoded.lastIndexOf(':')
    if (lastColon === -1) return false

    const payload = decoded.slice(0, lastColon)
    const sig = decoded.slice(lastColon + 1)

    const expectedSig = signPayload(payload)
    if (!safeCompare(sig, expectedSig)) {
      return false
    }

    const parts = payload.split(':')
    if (parts.length < 4) return false
    const [storedId, storedType, timestamp, storedPwSlot] = parts

    if (storedId !== deploymentId) return false

    // Bind the cookie to the auth type so a token minted under one mode (e.g. a
    // `public` share, which has an empty password slot) can't satisfy another
    // mode (e.g. `email` OTP) after the share's auth type is changed.
    if (storedType !== authType) return false

    const expectedPwSlot = passwordSlot(encryptedPassword)
    if (storedPwSlot !== expectedPwSlot) return false

    const createdAt = Number.parseInt(timestamp)
    const expireTime = 24 * 60 * 60 * 1000
    if (Date.now() - createdAt > expireTime) return false

    return true
  } catch (_e) {
    return false
  }
}

/** The kind of deployed resource an auth cookie/token belongs to. */
export type DeploymentAuthKind = 'chat' | 'file'

/** Canonical auth cookie name for a deployed resource (`{kind}_auth_{id}`). */
export function deploymentAuthCookieName(cookiePrefix: DeploymentAuthKind, id: string): string {
  return `${cookiePrefix}_auth_${id}`
}

/**
 * Sets an authentication cookie for a deployment
 */
export function setDeploymentAuthCookie(
  response: NextResponse,
  cookiePrefix: DeploymentAuthKind,
  deploymentId: string,
  authType: string,
  encryptedPassword?: string | null
): void {
  const token = generateAuthToken(deploymentId, authType, encryptedPassword)
  response.cookies.set({
    name: deploymentAuthCookieName(cookiePrefix, deploymentId),
    value: token,
    httpOnly: true,
    secure: !isDev,
    sameSite: 'lax',
    path: '/',
    maxAge: 60 * 60 * 24,
  })
}

/**
 * Checks if an email matches the allowed emails list (exact match or domain
 * match). Case-insensitive — email addresses are compared lowercased on both
 * sides, so callers don't need to normalize before calling.
 */
export function isEmailAllowed(email: string, allowedEmails: string[]): boolean {
  const normalizedEmail = email.trim().toLowerCase()
  const normalizedAllowed = allowedEmails.map((allowed) => allowed.trim().toLowerCase())

  if (normalizedAllowed.includes(normalizedEmail)) {
    return true
  }

  const atIndex = normalizedEmail.indexOf('@')
  if (atIndex > 0) {
    const domain = normalizedEmail.substring(atIndex + 1)
    if (domain && normalizedAllowed.some((allowed) => allowed === `@${domain}`)) {
      return true
    }
  }

  return false
}
