/**
 * POST /api/auth/switch-tenant — foundation-auth-rbac (Task 12).
 *
 * Requires an existing active membership in the target tenant, then issues a
 * fresh session (access + rotating refresh) scoped to it. Mounted behind
 * authMiddleware.
 *
 * When the target tenant enforces 2FA (or the user has 2FA enabled), mirrors
 * login.ts: device-trust bypass or pending_2fa / pending_2fa_setup — no full
 * cookies until 2FA is satisfied.
 */
import { Hono } from 'hono'
import { getCookie, deleteCookie } from 'hono/cookie'
import {
  generateOpaqueToken,
  hashToken,
  DEVICE_TRUST_COOKIE_NAME,
  PENDING_2FA_TTL_SECONDS,
  PENDING_2FA_TOKEN_PREFIX,
} from '@zync/auth'
import {
  createDb,
  getUser2FAStatus,
  getTenant2FASettings,
  findValidTrustedDevice,
  insertMagicLinkToken,
} from '@zync/db/queries'
import type { TenantId, UserId } from '@zync/types'
import type { AppEnv } from '../../types'
import { switchTenantSchema } from '../../schemas/auth'
import {
  issueSessionForTenant,
  issueSessionForTenantWith2FA,
  SessionCapExceededError,
} from '../../lib/issue-session'

export const switchTenantRoute = new Hono<AppEnv>()

switchTenantRoute.post('/switch-tenant', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user') {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const parsed = switchTenantSchema.safeParse(await c.req.json())
  if (!parsed.success) {
    return c.json({ error: 'Invalid request' }, 400)
  }

  const db = createDb(c.env)
  const userId = session.sub as UserId
  const targetTenantId = parsed.data.tenantId as TenantId

  const [user2fa, tenant2fa] = await Promise.all([
    getUser2FAStatus(db, userId),
    getTenant2FASettings(db, targetTenantId),
  ])

  const twoFactorEnabled = user2fa?.twoFactorEnabled ?? false
  const enforce2fa = tenant2fa?.enforce2fa ?? false

  if (twoFactorEnabled || enforce2fa) {
    const rawDeviceTrustToken = getCookie(c, DEVICE_TRUST_COOKIE_NAME)
    if (rawDeviceTrustToken) {
      const tokenHash = await hashToken(rawDeviceTrustToken)
      const trustedDevice = await findValidTrustedDevice(
        db,
        tokenHash,
        userId,
        targetTenantId,
      )
      if (trustedDevice) {
        let issued
        try {
          issued = await issueSessionForTenantWith2FA(
            c,
            db,
            userId,
            targetTenantId,
            { enforce2fa, twoFactorVerified: true },
          )
        } catch (error) {
          if (error instanceof SessionCapExceededError) {
            return c.json({ error: error.message }, 429)
          }
          throw error
        }
        if (!issued) {
          return c.json({ error: 'No active membership in target tenant' }, 403)
        }
        return c.json({ expiresAt: issued.expiresAt }, 200)
      }
      deleteCookie(c, DEVICE_TRUST_COOKIE_NAME, {
        httpOnly: true,
        secure: true,
        sameSite: 'Strict',
        domain: '.zync.is',
        path: '/',
      })
    }
  }

  if (twoFactorEnabled) {
    const plainToken = `${PENDING_2FA_TOKEN_PREFIX}${generateOpaqueToken()}`
    const tokenHash = await hashToken(plainToken)
    const expiresAt = new Date(Date.now() + PENDING_2FA_TTL_SECONDS * 1000)
    await insertMagicLinkToken(db, {
      tenantId: targetTenantId,
      userId,
      tokenHash,
      purpose: 'pending_2fa',
      expiresAt,
    })
    const disableRememberDevice = tenant2fa?.disable2faRememberDevice ?? false
    return c.json(
      {
        requires_2fa: true,
        session_token: plainToken,
        phone_suffix: user2fa?.twoFactorPhoneSuffix ?? null,
        allow_remember_device: !disableRememberDevice,
      },
      200,
    )
  }

  if (enforce2fa && !twoFactorEnabled) {
    const plainToken = `${PENDING_2FA_TOKEN_PREFIX}${generateOpaqueToken()}`
    const tokenHash = await hashToken(plainToken)
    const expiresAt = new Date(Date.now() + PENDING_2FA_TTL_SECONDS * 1000)
    await insertMagicLinkToken(db, {
      tenantId: targetTenantId,
      userId,
      tokenHash,
      purpose: 'pending_2fa_setup',
      expiresAt,
    })
    return c.json({ requires_2fa_setup: true, session_token: plainToken }, 200)
  }

  let issued
  try {
    issued = await issueSessionForTenant(c, db, userId, targetTenantId)
  } catch (error) {
    if (error instanceof SessionCapExceededError) {
      return c.json({ error: error.message }, 429)
    }
    throw error
  }
  if (!issued) {
    return c.json({ error: 'No active membership in target tenant' }, 403)
  }
  return c.json({ expiresAt: issued.expiresAt }, 200)
})
