/**
 * Customer portal password reset — tenant-portals (wave 9c, Task 5).
 *
 *   POST /api/portal/auth/forgot-password
 *   POST /api/portal/auth/reset-password
 */
import { Hono } from 'hono'
import {
  generateOpaqueToken,
  hashPassword,
  hashToken,
  revokeAllPortalSessions,
  timingSafeEqual,
} from '@zync/auth'
import { and, eq, gt, isNull } from '@zync/db'
import { createDb } from '@zync/db/queries'
import {
  customerContacts,
  customerPortalUsers,
  magicLinkTokens,
  tenants,
  users,
} from '@zync/db/schema'
import { sendEmail } from '@zync/notifications'
import type { AppEnv } from '../../types'
import { withDoHash } from '../../lib/password-hash-do'
import { getAppOrigins } from '../../lib/origins'
import { forgotPasswordSchema, resetPasswordSchema } from '../../schemas/portalAuth'

const RESET_LINK_TTL_MS = 60 * 60 * 1000

export const portalPasswordResetRoutes = new Hono<AppEnv>()

function checkOrigin(originHeader: string | undefined, environment: string | undefined): boolean {
  const allowedOrigins = getAppOrigins({ environment })
  return !!originHeader && allowedOrigins.has(originHeader)
}

function appBaseUrl(env: AppEnv['Bindings']): string {
  return env.APP_BASE_URL ?? 'https://app.zync.is'
}

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

async function findActivePortalUserByEmail(
  db: ReturnType<typeof createDb>,
  tenantId: string,
  email: string,
) {
  const [row] = await db
    .select({
      customerId: customerPortalUsers.customerId,
      userId: customerPortalUsers.userId,
      contactEmail: customerContacts.email,
    })
    .from(customerPortalUsers)
    .innerJoin(customerContacts, eq(customerContacts.id, customerPortalUsers.contactId))
    .where(
      and(
        eq(customerPortalUsers.tenantId, tenantId),
        eq(customerContacts.email, email),
        eq(customerPortalUsers.status, 'active'),
      ),
    )
    .limit(1)
  return row ?? null
}

portalPasswordResetRoutes.post('/forgot-password', async (c) => {
  if (!checkOrigin(c.req.header('Origin'), (c.env as { ENVIRONMENT?: string }).ENVIRONMENT)) {
    return c.json({ error: 'Forbidden: invalid Origin' }, 403)
  }

  const parsed = forgotPasswordSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) {
    return c.body(null, 204)
  }

  const { email, tenantSlug } = parsed.data
  const rlKey = `portal_forgot_pw:${email.toLowerCase()}`
  const rl = await (async () => {
    try {
      const result = await c.env.RATE_LIMITER_AUTH?.limit({ key: rlKey })
      return result ?? { success: true }
    } catch {
      return { success: true }
    }
  })()
  if (!rl.success) {
    return c.body(null, 204)
  }

  const db = createDb(c.env)
  const tenant = await resolveTenantBySlug(db, tenantSlug)
  if (!tenant) {
    return c.body(null, 204)
  }

  const portalUser = await findActivePortalUserByEmail(db, tenant.id, email)
  if (portalUser) {
    const plainToken = generateOpaqueToken()
    const tokenHash = await hashToken(plainToken)
    const expiresAt = new Date(Date.now() + RESET_LINK_TTL_MS)

    await db.insert(magicLinkTokens).values({
      tenantId: tenant.id,
      email,
      token: plainToken,
      tokenHash,
      purpose: 'portal_password_reset',
      expiresAt,
    })

    const link = `${appBaseUrl(c.env)}/portal/${tenantSlug}/reset-password?token=${encodeURIComponent(plainToken)}`
    const locale: 'he-IL' | 'en-US' = 'he-IL'

    await sendEmail(
      {
        to: email,
        templateKey: 'password-reset',
        vars: {
          subject: locale === 'he-IL' ? 'איפוס סיסמה לפורטל' : 'Reset your portal password',
          title: locale === 'he-IL' ? 'איפוס סיסמה' : 'Reset your password',
          body:
            locale === 'he-IL'
              ? 'לחצו על הכפתור כדי לבחור סיסמה חדשה לפורטל הלקוחות.'
              : 'Click the button to choose a new password for the customer portal.',
          resetUrl: link,
          ctaLabel: locale === 'he-IL' ? 'איפוס סיסמה' : 'Reset password',
          expiryNote: locale === 'he-IL' ? 'הקישור תקף לשעה אחת.' : 'This link expires in 1 hour.',
        },
        locale,
      },
      c.env,
    )
  }

  return c.body(null, 204)
})

portalPasswordResetRoutes.post('/reset-password', async (c) => {
  if (!checkOrigin(c.req.header('Origin'), (c.env as { ENVIRONMENT?: string }).ENVIRONMENT)) {
    return c.json({ error: 'Forbidden: invalid Origin' }, 403)
  }

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

  const { token, tenantSlug, newPassword } = parsed.data
  const db = createDb(c.env)
  const tenant = await resolveTenantBySlug(db, tenantSlug)
  if (!tenant) {
    return c.json({ error: 'Invalid or expired token' }, 400)
  }

  const tokenHash = await hashToken(token)
  const [tokenRow] = await db
    .select({
      id: magicLinkTokens.id,
      tokenHash: magicLinkTokens.tokenHash,
      email: magicLinkTokens.email,
      tenantId: magicLinkTokens.tenantId,
    })
    .from(magicLinkTokens)
    .where(
      and(
        eq(magicLinkTokens.tokenHash, tokenHash),
        eq(magicLinkTokens.purpose, 'portal_password_reset'),
        eq(magicLinkTokens.tenantId, tenant.id),
        isNull(magicLinkTokens.usedAt),
        gt(magicLinkTokens.expiresAt, new Date()),
      ),
    )
    .limit(1)

  if (!tokenRow || !timingSafeEqual(tokenHash, tokenRow.tokenHash) || !tokenRow.email) {
    return c.json({ error: 'Invalid or expired token' }, 400)
  }

  const portalUser = await findActivePortalUserByEmail(db, tenant.id, tokenRow.email)
  if (!portalUser) {
    return c.json({ error: 'Invalid or expired token' }, 400)
  }

  const consumed = await db
    .update(magicLinkTokens)
    .set({ usedAt: new Date() })
    .where(and(eq(magicLinkTokens.id, tokenRow.id), isNull(magicLinkTokens.usedAt)))
    .returning({ id: magicLinkTokens.id })

  if (consumed.length === 0) {
    return c.json({ error: 'Invalid or expired token' }, 400)
  }

  const passwordHash = await hashPassword(newPassword, withDoHash(c.env))
  await db.update(users).set({ passwordHash }).where(eq(users.id, portalUser.userId))

  await revokeAllPortalSessions(db, tenant.id, portalUser.customerId)

  return c.body(null, 204)
})
