/**
 * Portal tickets routes — tenant-portals (wave 9d, Task 8).
 *
 * GET  /api/portal/tickets
 * POST /api/portal/tickets
 * GET  /api/portal/tickets/:id
 * POST /api/portal/tickets/:id/reply
 */
import { Hono } from 'hono'
import { z } from 'zod'
import {
  listPortalTicketSummaries,
  getPortalTicketForCustomer,
  getTicket,
  createPortalTicketWithMessage,
  createTicketMessage,
  listTicketMessages,
  listTicketCategories,
  getTicketCategoryById,
  addTicketMessageAttachment,
  getPortalProfile,
  recordSystemCommunication,
  findUserById,
} from '@zync/db/queries'
import type { UserId } from '@zync/types'
import { sendEmail } from '@zync/notifications'
import type { AppEnv } from '../../types'
import { portalAuthMiddleware, type PortalAuthVariables } from '../../middleware/portalAuth'
import {
  createPortalTicketSchema,
  replyPortalTicketSchema,
} from '../../schemas/portalTickets'
import {
  resolvePortalTicketAttachments,
  validatePortalTicketAttachmentUpload,
  PortalTicketAttachmentValidationError,
} from '../../lib/portal-ticket-attachments'
import {
  buildPortalFileKey,
  createSignedPutUrl,
} from '../../lib/portal-file-storage'
import { requirePortalVisibility } from './visibility'

type PortalDataEnv = {
  Bindings: AppEnv['Bindings']
  Variables: PortalAuthVariables
}

export const portalTicketsRoutes = new Hono<PortalDataEnv>()

portalTicketsRoutes.use('*', portalAuthMiddleware)

const ticketAttachmentUploadUrlSchema = z.object({
  filename: z.string().min(1),
  mime_type: z.string().min(1),
  file_size_bytes: z.number().int().positive(),
})

portalTicketsRoutes.get('/', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const items = await listPortalTicketSummaries(db, portal.tenantId, portal.customerId)
  return c.json({ items }, 200)
})

portalTicketsRoutes.get('/categories', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const items = await listTicketCategories(db, portal.tenantId)
  return c.json({ items }, 200)
})

portalTicketsRoutes.post('/attachments/upload-url', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')

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

  const { filename, mime_type, file_size_bytes } = parsed.data
  try {
    validatePortalTicketAttachmentUpload({ mime_type, file_size_bytes })
  } catch (err) {
    if (err instanceof PortalTicketAttachmentValidationError) {
      return c.json({ error: err.message, code: err.code }, 400)
    }
    throw err
  }

  const r2Key = buildPortalFileKey(portal.tenantId, portal.customerId, filename)
  const uploadUrl = await createSignedPutUrl(c.env, r2Key, {
    contentType: mime_type,
    contentLength: file_size_bytes,
  })
  return c.json({ uploadUrl, key: r2Key }, 200)
})

portalTicketsRoutes.post('/', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')

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

  const category = await getTicketCategoryById(db, portal.tenantId, parsed.data.categoryId)
  if (!category) {
    return c.json({ error: 'Invalid category' }, 400)
  }

  const profile = await getPortalProfile(db, portal.tenantId, portal.customerId, portal.userId)

  let attachments
  try {
    attachments = await resolvePortalTicketAttachments(
      c.env,
      portal.tenantId,
      portal.customerId,
      parsed.data.attachmentKeys,
    )
  } catch (err) {
    return c.json({ error: err instanceof Error ? err.message : 'Invalid attachment' }, 400)
  }

  const { ticket, messageId } = await createPortalTicketWithMessage(
    db,
    portal.tenantId,
    portal.customerId,
    {
      subject: parsed.data.subject,
      body: parsed.data.body,
      categoryId: parsed.data.categoryId,
      authorName: profile?.name ?? null,
    },
  )

  for (const att of attachments) {
    await addTicketMessageAttachment(db, portal.tenantId, {
      message_id: messageId,
      filename: att.filename,
      r2_key: att.r2Key,
      url: att.url,
      size_bytes: att.sizeBytes,
      mime_type: att.mimeType,
    })
  }

  return c.json({ ticket }, 201)
})

portalTicketsRoutes.get('/:id', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const ticketId = c.req.param('id')

  const ticket = await getPortalTicketForCustomer(
    db,
    portal.tenantId,
    portal.customerId,
    ticketId,
  )
  if (!ticket) return c.json({ error: 'Not found' }, 404)

  const rawMessages = await listTicketMessages(db, portal.tenantId, ticketId)
  const messages = rawMessages.map((m) => ({
    id: m.id,
    authorType: m.author_type,
    authorName: m.author_name,
    body: m.content.replace(/<[^>]+>/g, ''),
    createdAt: m.created_at,
  }))

  return c.json({ ticket: { ...ticket, subject: ticket.title }, messages }, 200)
})

portalTicketsRoutes.post('/:id/reply', async (c) => {
  const visibilityError = await requirePortalVisibility(c, 'show_tickets')
  if (visibilityError) return visibilityError
  const portal = c.get('portal')
  const db = c.get('db')
  const ticketId = c.req.param('id')

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

  const ticket = await getPortalTicketForCustomer(
    db,
    portal.tenantId,
    portal.customerId,
    ticketId,
  )
  if (!ticket) return c.json({ error: 'Not found' }, 404)
  if (ticket.status === 'closed') {
    return c.json({ error: 'Ticket is closed' }, 409)
  }

  const profile = await getPortalProfile(db, portal.tenantId, portal.customerId, portal.userId)

  let attachments
  try {
    attachments = await resolvePortalTicketAttachments(
      c.env,
      portal.tenantId,
      portal.customerId,
      parsed.data.attachmentKeys,
    )
  } catch (err) {
    return c.json({ error: err instanceof Error ? err.message : 'Invalid attachment' }, 400)
  }

  const message = await createTicketMessage(db, portal.tenantId, ticketId, {
    author_type: 'customer',
    author_name: profile?.name ?? null,
    content: parsed.data.body,
    source: 'portal',
  })

  for (const att of attachments) {
    await addTicketMessageAttachment(db, portal.tenantId, {
      message_id: message.id,
      filename: att.filename,
      r2_key: att.r2Key,
      url: att.url,
      size_bytes: att.sizeBytes,
      mime_type: att.mimeType,
    })
  }

  await recordSystemCommunication(db, {
    tenantId: portal.tenantId,
    customerId: portal.customerId,
    direction: 'inbound',
    channel: 'ticket',
    subject: ticket.title,
    body: parsed.data.body,
    relatedId: ticketId,
    relatedType: 'ticket',
  })

  const fullTicket = await getTicket(db, portal.tenantId, ticketId)
  if (fullTicket?.assignee_id) {
    const staff = await findUserById(db, fullTicket.assignee_id as UserId)
    if (staff?.email) {
      await sendEmail(
        {
          to: staff.email,
          templateKey: 'invoice_reminder',
          vars: {
            subject: `Portal reply: ${ticket.title}`,
            title: 'New portal ticket reply',
            body: parsed.data.body,
          },
          locale: 'he-IL',
        },
        c.env,
      ).catch(() => {})
    }
  }

  return c.json({ message }, 201)
})
