/**
 * Customer portal auth middleware — tenant-portals (wave 9b, Task 3).
 *
 * Responsibilities, in order:
 *   1. Origin allow-list on state-mutating methods (POST/PATCH/DELETE) — 403
 *      runs BEFORE any auth work (CSRF defence-in-depth alongside SameSite).
 *   2. Extract the portal JWT from the `zync_portal_session` httpOnly cookie.
 *   3. verifyPortalToken (HS256) — 401 { code: 'portal_session_expired' } on failure.
 *   4. Assert role === 'portal_customer'; reject staff JWTs.
 *   5. hashToken + findActivePortalSession — 401 when row missing/revoked/expired.
 *   6. Bind tenantSlug path param to payload.tenantId — 403 on cross-tenant mismatch.
 *   7. Populate c.set('portal', { tenantId, customerId, userId, portalSessionId }).
 */
import type { MiddlewareHandler } from 'hono'
import { eq } from '@zync/db'
import {
  findActivePortalSession,
  hashToken,
  parsePortalCookie,
  verifyPortalToken,
} from '@zync/auth'
import { createDb, type Db } from '@zync/db/queries'
import { tenants } from '@zync/db'
import type { AppEnv } from '../types'
import { getAppOrigins } from '../lib/origins'

const MUTATING = new Set(['POST', 'PATCH', 'DELETE'])

export type PortalContext = {
  tenantId: string
  customerId: string
  userId: string
  portalSessionId: string
}

/** Hono Variables contract the portal routes depend on. */
export type PortalAuthVariables = {
  portal: PortalContext
  db: Db
}

async function resolveTenantBySlug(db: Db, slug: string): Promise<{ id: string } | null> {
  const [row] = await db
    .select({ id: tenants.id })
    .from(tenants)
    .where(eq(tenants.slug, slug))
    .limit(1)
  return row ?? null
}

/**
 * Returns a 403 body when `resourceCustomerId` is outside the authenticated portal
 * customer's scope; null when the resource is in scope.
 */
export function requirePortalScope(
  resourceCustomerId: string,
  portal: PortalContext,
): { error: 'Forbidden' } | null {
  if (resourceCustomerId !== portal.customerId) {
    return { error: 'Forbidden' }
  }
  return null
}

async function authenticatePortalSession(
  c: Parameters<MiddlewareHandler<AppEnv & { Variables: PortalAuthVariables }>>[0],
  options: { requireSlugBinding: boolean },
): Promise<Response | null> {
  if (MUTATING.has(c.req.method)) {
    const origin = c.req.header('Origin')
    const allowedOrigins = getAppOrigins({
      environment: (c.env as { ENVIRONMENT?: string }).ENVIRONMENT,
    })
    if (!origin || !allowedOrigins.has(origin)) {
      return c.json({ error: 'Forbidden: invalid Origin' }, 403)
    }
  }

  const token = parsePortalCookie(c.req.header('Cookie'))
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  let payload
  try {
    payload = await verifyPortalToken(token, c.env.JWT_SECRET)
  } catch {
    return c.json({ code: 'portal_session_expired', error: 'Unauthorized' }, 401)
  }

  if (payload.role !== 'portal_customer') {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const db = createDb(c.env)
  c.set('db', db)

  const tokenHash = await hashToken(token)
  const activeSession = await findActivePortalSession(db, tokenHash)
  if (!activeSession) {
    return c.json({ code: 'portal_session_expired', error: 'Unauthorized' }, 401)
  }

  if (options.requireSlugBinding) {
    const tenantSlug = c.req.param('tenantSlug')
    if (!tenantSlug) {
      return c.json({ error: 'Forbidden' }, 403)
    }

    const tenant = await resolveTenantBySlug(db, tenantSlug)
    if (!tenant || tenant.id !== payload.tenantId) {
      return c.json({ error: 'Forbidden' }, 403)
    }
  } else if (activeSession.tenantId !== payload.tenantId) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  c.set('portal', {
    tenantId: activeSession.tenantId,
    customerId: activeSession.customerId,
    userId: activeSession.userId,
    portalSessionId: activeSession.id,
  })

  return null
}

/**
 * Stateful portal gate for data routes.
 * When `{tenantSlug}` is present in the path, verifies slug ↔ JWT tenantId.
 * Otherwise binds tenant authoritatively from the active `portal_sessions` row.
 */
export const portalAuthMiddleware: MiddlewareHandler<
  AppEnv & { Variables: PortalAuthVariables }
> = async (c, next) => {
  const denied = await authenticatePortalSession(c, {
    requireSlugBinding: Boolean(c.req.param('tenantSlug')),
  })
  if (denied) return denied
  await next()
}

/**
 * Stateful portal gate without URL slug — refresh/logout only.
 * Tenant is bound from the authoritative `portal_sessions` row + JWT `tenantId`.
 */
export const portalSessionAuthMiddleware: MiddlewareHandler<
  AppEnv & { Variables: PortalAuthVariables }
> = async (c, next) => {
  const denied = await authenticatePortalSession(c, { requireSlugBinding: false })
  if (denied) return denied
  await next()
}
