import type { H3Event } from 'h3'
import { getHeaders, getRequestHeader } from 'h3'
import type { AuthSession } from '../types'
import { getAuth } from '../lib/auth-instance'
import { useDB, eq, and, gt } from './database'
import { user as userTable, session as sessionTable } from '../database/schema/auth'

/**
 * Extract Bearer token from Authorization header
 */
function extractBearerToken(event: H3Event): string | null {
  const authHeader = getRequestHeader(event, 'authorization') || ''
  const match = authHeader.match(/^Bearer\s+(.+)$/i)
  if (!match) return null

  const tokenWithSig = decodeURIComponent(match[1] || '')
  return tokenWithSig.split('.')[0] || tokenWithSig
}

/**
 * Try to resolve session using cookie-based resolution with token
 * Handles multiple cookie name variants
 */
async function tryCookieBasedResolution(
  auth: ReturnType<typeof getAuth>,
  token: string
): Promise<AuthSession | null> {
  // Try all cookie name variants including __Secure- prefix
  const cookieVariants = [
    `better-auth.session_token=${token}`,
    `better-auth.session-token=${encodeURIComponent(token)}`,
    `__Secure-better-auth.session_token=${token}`,
    `__Secure-better-auth.session-token=${encodeURIComponent(token)}`
  ]

  for (const cookieHeader of cookieVariants) {
    const session = await auth.api.getSession({
      headers: new Headers({ cookie: cookieHeader })
    }) as unknown as AuthSession

    if (session?.user) {
      return session
    }
  }

  return null
}

/**
 * Resolve session from database using token
 * Direct database lookup as final fallback
 */
async function resolveSessionFromDatabase(
  token: string
): Promise<AuthSession | null> {
  const db = useDB()
  const now = new Date()
  const [row] = await db
    .select({
      userId: userTable.id,
      name: userTable.name,
      email: userTable.email,
      image: userTable.image,
      emailVerified: userTable.emailVerified,
      superAdmin: userTable.superAdmin,
      banned: userTable.banned,
      bannedUntil: userTable.bannedUntil,
      sessionId: sessionTable.id,
      sessionUserId: sessionTable.userId,
      expiresAt: sessionTable.expiresAt
    })
    .from(sessionTable)
    .innerJoin(userTable, eq(userTable.id, sessionTable.userId))
    .where(and(eq(sessionTable.token, token), gt(sessionTable.expiresAt, now)))
    .limit(1)

  if (!row) {
    return null
  }

  // Build an AuthSession-compatible object
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const built: any = {
    user: {
      id: row.userId,
      name: row.name,
      email: row.email,
      image: row.image,
      emailVerified: row.emailVerified,
      superAdmin: row.superAdmin,
      banned: row.banned,
      bannedUntil: row.bannedUntil
    },
    session: {
      id: row.sessionId,
      userId: row.sessionUserId,
      expiresAt: row.expiresAt
    }
  }

  return built as AuthSession
}

/**
 * Unified session resolution that handles:
 * - Cookie-based sessions (web browsers)
 * - Bearer token sessions (Capacitor apps)
 * - Multiple cookie name variants
 *
 * @param event - H3 event object
 * @returns Resolved session or null if no session found
 */
export async function resolveSession(event: H3Event): Promise<AuthSession | null> {
  const auth = getAuth()
  const headers = new Headers(getHeaders(event) as HeadersInit)
  const cookieHeader = headers.get('cookie')

  // Extract session token from cookie - try all variants including __Secure- prefix
  const sessionTokenMatch = cookieHeader?.match(/(?:__Secure-)?better-auth\.session[_-]token=([^;]+)/)
  const sessionToken = sessionTokenMatch?.[1] ? decodeURIComponent(sessionTokenMatch[1]!).split('.')[0] : null

  // Try Better Auth's native session resolution first
  let session: AuthSession | null = await auth.api.getSession({ headers }) as unknown as AuthSession

  if (session?.user) {
    return session
  }

  // If Better Auth didn't find the session, try direct database lookup
  if (sessionToken) {
    const dbSession = await resolveSessionFromDatabase(sessionToken)
    if (dbSession?.user) {
      return dbSession
    }
  }

  // Fallback: Extract Bearer token and resolve manually
  const token = extractBearerToken(event)
  if (!token) {
    return null
  }

  // Try cookie-based resolution with token
  session = await tryCookieBasedResolution(auth, token)
  if (session?.user) {
    return session
  }

  // Final fallback: Direct database lookup
  return await resolveSessionFromDatabase(token)
}
