/**
 * Portal settings routes — customer-portal-settings-ui (spec 136, wave 11).
 * Mounted at /api/settings/portal.
 *
 * GET  /                  → PortalSettingsDTO (requires settings:read)
 * PATCH /                 → PortalSettingsDTO (requires settings:write)
 * GET  /preview-token     → { token, expires_at } (requires settings:read)
 * GET  /users             → PortalUserRow[] (requires settings:read)
 * POST /users/:contactId/revoke   → 200 (requires settings:write)
 * POST /users/:contactId/resend   → 200 (requires settings:write)
 */
import { Hono, type Context } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import type { TenantId, UserId } from '@zync/types'
import { authMiddleware } from '../../middleware/auth'
import { requirePermission } from '../../middleware/guards'
import {
  getPortalSettings,
  upsertPortalSettings,
  listTenantPortalUsers,
  revokePortalUser,
  getContactById,
  getRoleByName,
  createInvitation,
  appendCustomerCommunication,
} from '@zync/db/queries'
import { signSignedToken, generateOpaqueToken, hashToken } from '@zync/auth'
import { sendPortalInvitationEmail } from '../../adapters/email-customers'

// ── Validation ────────────────────────────────────────────────────────────────

const PORTAL_VISIBILITY_KEYS = [
  'show_invoices',
  'show_proposals',
  'show_projects',
  'show_tickets',
  'show_files',
  'show_contracts',
  'show_time_summary',
] as const

const portalVisibilitySchema = z
  .record(z.enum(PORTAL_VISIBILITY_KEYS), z.boolean())
  .optional()

const hexColorRegex = /^#[0-9a-fA-F]{6}$/

const updatePortalSettingsSchema = z
  .object({
    portal_enabled: z.boolean().optional(),
    portal_access_method: z.enum(['magic_link', 'password']).optional(),
    portal_name: z.string().max(120).nullable().optional(),
    portal_welcome_text: z.string().max(500).nullable().optional(),
    portal_primary_color_hex: z
      .string()
      .regex(hexColorRegex, 'Must be a 6-digit hex color (#rrggbb)')
      .nullable()
      .optional(),
    portal_logo_url: z.string().url().nullable().optional(),
    portal_can_submit_tickets: z.boolean().optional(),
    portal_can_upload_files: z.boolean().optional(),
    portal_show_team_members: z.boolean().optional(),
    portal_visibility: portalVisibilitySchema,
  })
  .strict()

// ── Router ────────────────────────────────────────────────────────────────────

export const portalSettingsRoute = new Hono<AppEnv>()
const INVITE_TTL_MS = 1000 * 60 * 60 * 24 * 7

portalSettingsRoute.use('*', authMiddleware)

async function sendPortalInviteForContact(
  c: Context<AppEnv>,
  tenantId: string,
  actorId: string,
  contactId: string,
) {
  const db = c.get('db')
  const contact = await getContactById(db, tenantId, contactId)
  if (!contact) {
    return c.json({ error: 'Not found' }, 404)
  }
  if (!contact.email) {
    return c.json({ error: 'Contact is missing an email address' }, 400)
  }

  const viewerRole = await getRoleByName(db, tenantId as TenantId, 'VIEWER')
  if (!viewerRole) {
    return c.json({ error: 'Tenant roles not seeded' }, 500)
  }

  const plainToken = generateOpaqueToken()
  const tokenHash = await hashToken(plainToken)
  const expiresAt = new Date(Date.now() + INVITE_TTL_MS)
  const invitation = await createInvitation(db, {
    tenantId: tenantId as TenantId,
    email: contact.email,
    roleId: viewerRole.id,
    tokenHash,
    expiresAt,
    invitedBy: actorId as UserId,
    actorIp: c.req.header('CF-Connecting-IP') ?? null,
    requestId: c.req.header('CF-Ray') ?? null,
  })

  await appendCustomerCommunication(db, tenantId, contact.customerId, {
    direction: 'outbound',
    channel: 'system',
    subject: 'Portal invitation sent',
    body: `Portal invitation sent to ${contact.email}`,
    fromAddress: null,
    toAddress: contact.email,
    relatedId: invitation.id,
    relatedType: 'invitation',
    createdBy: actorId,
  })

  await sendPortalInvitationEmail(c.env, {
    to: contact.email,
    token: plainToken,
    tenantId: tenantId as TenantId,
    customerId: contact.customerId,
    contactId,
    invitedBy: actorId as UserId,
  })

  return c.json({ ok: true }, 200)
}

// ── GET /api/settings/portal ──────────────────────────────────────────────────

portalSettingsRoute.get('/', requirePermission('settings: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 dto = await getPortalSettings(db, session.tid)
  return c.json(dto, 200)
})

// ── PATCH /api/settings/portal ────────────────────────────────────────────────

portalSettingsRoute.patch('/', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const body = await c.req.json().catch(() => null)
  const parsed = updatePortalSettingsSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
  }

  const db = c.get('db')
  const updated = await upsertPortalSettings(db, session.tid, session.sub, parsed.data)
  return c.json(updated, 200)
})

// ── GET /api/settings/portal/preview-token ────────────────────────────────────

portalSettingsRoute.get('/preview-token', requirePermission('settings:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const expiresInSeconds = 900 // 15 minutes
  const token = await signSignedToken(
    { tenantId: session.tid, role: 'portal_preview' },
    c.env.JWT_SECRET,
    expiresInSeconds,
  )

  const expiresAt = new Date(Date.now() + expiresInSeconds * 1000).toISOString()
  return c.json({ token, expires_at: expiresAt }, 200)
})

// ── GET /api/settings/portal/users ───────────────────────────────────────────

portalSettingsRoute.get('/users', requirePermission('settings: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 users = await listTenantPortalUsers(db, session.tid)
  return c.json(users, 200)
})

// ── POST /api/settings/portal/users/:contactId/revoke ────────────────────────

portalSettingsRoute.post(
  '/users/:contactId/revoke',
  requirePermission('settings:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const { contactId } = c.req.param()
    const db = c.get('db')
    await revokePortalUser(db, session.tid, contactId)
    return c.json({ ok: true }, 200)
  },
)

portalSettingsRoute.post(
  '/users/:contactId/resend-invite',
  requirePermission('settings:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    return sendPortalInviteForContact(c, session.tid, session.sub, c.req.param('contactId'))
  },
)
