/**
 * Portal invitation & portal-user routes — customers module (Task 6).
 *
 * POST   /api/customers/:id/contacts/:cid/invite-portal   send portal invitation
 * GET    /api/customers/:id/portal-users                  list portal users
 * POST   /api/customers/:id/portal-users/:uid/freeze      freeze portal access
 * POST   /api/customers/:id/portal-users/:uid/unfreeze    unfreeze portal access
 *
 * Invitation flow:
 *   1. Generate opaque token; store SHA-256 hash in `invitations` table.
 *   2. Email plaintext token to contact (via QUEUE adapter).
 *   3. Record a `customer_communications` system row (channel=system, direction=outbound).
 *
 * Freeze: sets status=frozen + bumps user_version (JWT revoked within ≤60s).
 * Unfreeze: reverses to status=active.
 */
import { Hono } from 'hono'
import { generateOpaqueToken, hashToken, revokeAllPortalSessions } from '@zync/auth'
import {
  appendCustomerCommunication,
  createInvitation,
  getContactById,
  getRoleByName,
  listPortalUsers,
  setPortalUserStatus,
} from '@zync/db/queries'
import type { TenantId, UserId } from '@zync/types'
import type { AppEnv } from '../../types'
import { requirePermission } from '../../middleware/guards'
import { bumpUserVersion } from '../../middleware/user-version'
import { sendPortalInvitationEmail } from '../../adapters/email-customers'

export const portalRoute = new Hono<AppEnv>()

const INVITE_TTL_MS = 1000 * 60 * 60 * 24 * 7 // 7 days

// POST /api/customers/:id/contacts/:cid/invite-portal
portalRoute.post(
  '/:id/contacts/:cid/invite-portal',
  requirePermission('customers:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const customerId = c.req.param('id')
    const contactId = c.req.param('cid')
    const tenantId = session.tid as TenantId

    const db = c.get('db')
    const contact = await getContactById(db, tenantId, contactId)
    if (!contact || contact.customerId !== customerId) {
      return c.json({ error: 'Not found' }, 404)
    }
    const email = contact.email

    // Find VIEWER role for this tenant (portal users get viewer access)
    const viewerRole = await getRoleByName(db, tenantId, 'VIEWER')
    if (!viewerRole) {
      return c.json({ error: 'Tenant roles not seeded' }, 500)
    }

    // Generate invitation token — only hash stored; plaintext emailed
    const plainToken = generateOpaqueToken()
    const tokenHash = await hashToken(plainToken)
    const expiresAt = new Date(Date.now() + INVITE_TTL_MS)

    const inv = await createInvitation(db, {
      tenantId,
      email,
      roleId: viewerRole.id,
      tokenHash,
      expiresAt,
      invitedBy: session.sub as UserId,
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    })

    // Record system communication row (channel=system, direction=outbound)
    await appendCustomerCommunication(db, tenantId, customerId, {
      direction: 'outbound',
      channel: 'system',
      subject: 'Portal invitation sent',
      body: `Portal invitation sent to ${email}`,
      fromAddress: null,
      toAddress: email,
      relatedId: inv.id,
      relatedType: 'invitation',
      createdBy: session.sub,
    })

    // Send portal invitation email via queue adapter
    await sendPortalInvitationEmail(c.env, {
      to: email,
      token: plainToken,
      tenantId,
      customerId,
      contactId,
      invitedBy: session.sub,
    })

    return c.json({ invitationId: inv.id }, 201)
  },
)

// GET /api/customers/:id/portal-users
portalRoute.get('/:id/portal-users', requirePermission('customers:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const db = c.get('db')
  const portalUsers = await listPortalUsers(db, session.tid, c.req.param('id'))
  return c.json(portalUsers, 200)
})

// POST /api/customers/:id/portal-users/:uid/freeze
portalRoute.post(
  '/:id/portal-users/:uid/freeze',
  requirePermission('users:invite'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const db = c.get('db')
    try {
      const portalUser = await setPortalUserStatus(
        db,
        session.tid,
        c.req.param('id'),
        c.req.param('uid'),
        'frozen',
        session.sub,
      )

      // Bump user_version so JWT is revoked within ≤60s
      await bumpUserVersion(c.env, portalUser.userId)

      // Immediately invalidate all portal_sessions for this customer
      await revokeAllPortalSessions(db, session.tid, c.req.param('id'))

      return c.json(portalUser, 200)
    } catch (err) {
      if (err instanceof Error && err.message === 'Portal user not found') {
        return c.json({ error: 'Not found' }, 404)
      }
      throw err
    }
  },
)

// POST /api/customers/:id/portal-users/:uid/unfreeze
portalRoute.post(
  '/:id/portal-users/:uid/unfreeze',
  requirePermission('users:invite'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const db = c.get('db')
    try {
      const portalUser = await setPortalUserStatus(
        db,
        session.tid,
        c.req.param('id'),
        c.req.param('uid'),
        'active',
        session.sub,
      )

      return c.json(portalUser, 200)
    } catch (err) {
      if (err instanceof Error && err.message === 'Portal user not found') {
        return c.json({ error: 'Not found' }, 404)
      }
      throw err
    }
  },
)
