/**
 * Staff portal-files routes — portal-file-sharing (wave-11 leaf-C).
 *
 * Mounted at /api/portal-files and /api/customers/:customerId/portal-files.
 * All routes require authMiddleware + appropriate permission.
 *
 * Two-step upload: POST /upload-url → client PUT to R2 → POST / to record metadata.
 * DELETE removes DB row + R2 object atomically.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import {
  listPortalFilesForStaff,
  getPortalFileById,
  createPortalFile,
  updatePortalFile,
  deletePortalFile,
  InvalidUploaderError,
  assertTenantOwnsCustomer,
  assertTenantOwnsProject,
  invalidTenantReferenceBody,
} from '@zync/db/queries'
import {
  buildPortalFileKey,
  validateUpload,
  validatePortalFileR2Key,
  createSignedPutUrl,
  createSignedDownloadUrl,
  deleteR2Object,
  PortalFileValidationError,
} from '../lib/portal-file-storage'

// ── Validation schemas ────────────────────────────────────────────────────────

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

const createPortalFileSchema = z.object({
  customer_id: z.string().uuid(),
  project_id: z.string().uuid().optional(),
  r2_key: z.string().min(1),
  filename: z.string().min(1),
  mime_type: z.string().min(1),
  file_size_bytes: z.number().int().positive(),
  description: z.string().optional(),
  visible_to_portal: z.boolean(),
  expires_at: z.string().datetime().optional(),
})

const updatePortalFileSchema = z.object({
  description: z.string().optional(),
  visible_to_portal: z.boolean().optional(),
  expires_at: z.string().datetime().nullable().optional(),
})

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

export const portalFilesRoute = new Hono<AppEnv>()

portalFilesRoute.use('*', authMiddleware)

// ── GET /api/customers/:customerId/portal-files ───────────────────────────────

portalFilesRoute.get('/customers/:customerId/portal-files', requirePermission('customers:read'), 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('customerId')
  const url = new URL(c.req.url)
  const projectId = url.searchParams.get('project_id') ?? undefined
  const page = Math.max(1, parseInt(url.searchParams.get('page') ?? '1', 10))
  const limit = 50
  const offset = (page - 1) * limit

  const db = c.get('db')
  const items = await listPortalFilesForStaff(db, session.tid, {
    customerId,
    projectId,
    limit,
    offset,
  })

  return c.json({ items }, 200)
})

// ── POST /api/portal-files/upload-url ────────────────────────────────────────

portalFilesRoute.post('/portal-files/upload-url', requirePermission('customers: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 = uploadUrlSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }

  const { filename, mime_type, file_size_bytes, customer_id } = parsed.data
  const db = c.get('db')

  if (!(await assertTenantOwnsCustomer(db, session.tid, customer_id))) {
    return c.json(invalidTenantReferenceBody('customer_id'), 400)
  }

  try {
    validateUpload({ mime_type, file_size_bytes })
  } catch (err) {
    if (err instanceof PortalFileValidationError) {
      return c.json({ error: err.message, code: err.code }, 400)
    }
    throw err
  }

  const r2Key = buildPortalFileKey(session.tid, customer_id, filename)
  const uploadUrl = await createSignedPutUrl(c.env, r2Key, { contentType: mime_type })

  return c.json({ upload_url: uploadUrl, r2_key: r2Key }, 200)
})

// ── POST /api/portal-files ────────────────────────────────────────────────────

portalFilesRoute.post('/portal-files', requirePermission('customers: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 = createPortalFileSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }

  const { customer_id, project_id, r2_key, filename, mime_type, file_size_bytes, description, visible_to_portal, expires_at } = parsed.data
  const db = c.get('db')

  if (!(await assertTenantOwnsCustomer(db, session.tid, customer_id))) {
    return c.json(invalidTenantReferenceBody('customer_id'), 400)
  }
  if (!(await assertTenantOwnsProject(db, session.tid, project_id))) {
    return c.json(invalidTenantReferenceBody('project_id'), 400)
  }

  try {
    validateUpload({ mime_type, file_size_bytes })
    validatePortalFileR2Key(session.tid, customer_id, r2_key)
  } catch (err) {
    if (err instanceof PortalFileValidationError) {
      return c.json({ error: err.message, code: err.code }, 400)
    }
    throw err
  }

  try {
    const file = await createPortalFile(db, {
      tenantId: session.tid,
      customerId: customer_id,
      projectId: project_id ?? null,
      uploadedBy: session.sub,
      uploadedByPortalUser: null,
      r2Key: r2_key,
      filename,
      mimeType: mime_type,
      fileSizeBytes: file_size_bytes,
      description: description ?? null,
      visibleToPortal: visible_to_portal,
      expiresAt: expires_at ? new Date(expires_at) : null,
    })
    return c.json({ file }, 201)
  } catch (err) {
    if (err instanceof InvalidUploaderError) {
      return c.json({ error: err.message }, 400)
    }
    throw err
  }
})

// ── PATCH /api/portal-files/:id ───────────────────────────────────────────────

portalFilesRoute.patch('/portal-files/:id', requirePermission('customers:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

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

  const db = c.get('db')
  const patch = parsed.data
  const updated = await updatePortalFile(db, session.tid, id, {
    description: patch.description,
    visibleToPortal: patch.visible_to_portal,
    expiresAt: patch.expires_at === null ? null : patch.expires_at ? new Date(patch.expires_at) : undefined,
  })

  if (!updated) {
    return c.json({ error: 'Not found' }, 404)
  }
  return c.json({ file: updated }, 200)
})

// ── DELETE /api/portal-files/:id ──────────────────────────────────────────────

portalFilesRoute.delete('/portal-files/:id', requirePermission('customers:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const id = c.req.param('id')
  const db = c.get('db')
  const deleted = await deletePortalFile(db, session.tid, id)
  if (!deleted) {
    return c.json({ error: 'Not found' }, 404)
  }

  await deleteR2Object(c.env, deleted.r2Key)
  return c.body(null, 204)
})

// ── GET /api/portal-files/:id/download ───────────────────────────────────────
// Shared: staff (tenantId check) or portal session (customerId + visibility check).

portalFilesRoute.get('/portal-files/:id/download', async (c) => {
  const id = c.req.param('id')
  const session = c.get('session')

  // Staff path
  if (session && session.type === 'user' && session.tid) {
    const db = c.get('db')
    const file = await getPortalFileById(db, session.tid, id)
    if (!file) {
      return c.json({ error: 'Not found' }, 404)
    }
    try {
      validatePortalFileR2Key(session.tid, file.customerId, file.r2Key)
    } catch (err) {
      if (err instanceof PortalFileValidationError) {
        return c.json({ error: err.message, code: err.code }, 403)
      }
      throw err
    }
    const url = await createSignedDownloadUrl(c.env, file.r2Key, { filename: file.filename })
    return c.json({ url, filename: file.filename }, 200)
  }

  // Portal path: read portal_session cookie
  const cookie = c.req.header('Cookie') ?? ''
  const tokenMatch = /portal_session=([^;]+)/.exec(cookie)
  if (tokenMatch) {
    const { verifyJwt } = await import('@zync/auth')
    try {
      const payload = await verifyJwt(tokenMatch[1]!, c.env.JWT_SECRET)
      if (payload && payload['type'] === 'portal') {
        const tenantId = payload['tid'] as string
        const customerId = payload['customerId'] as string
        const db = c.get('db')
        const file = await getPortalFileById(db, tenantId, id)
        if (!file) return c.json({ error: 'Not found' }, 404)
        if (file.customerId !== customerId) return c.json({ error: 'Forbidden' }, 403)
        if (!file.visibleToPortal) return c.json({ error: 'Forbidden' }, 403)
        if (file.expiresAt && new Date(file.expiresAt) < new Date()) {
          return c.json({ error: 'File has expired' }, 403)
        }
        try {
          validatePortalFileR2Key(tenantId, file.customerId, file.r2Key)
        } catch (err) {
          if (err instanceof PortalFileValidationError) {
            return c.json({ error: err.message, code: err.code }, 403)
          }
          throw err
        }
        const url = await createSignedDownloadUrl(c.env, file.r2Key, { filename: file.filename })
        return c.json({ url, filename: file.filename }, 200)
      }
    } catch {
      // fall through to 401
    }
  }

  return c.json({ error: 'Unauthorized' }, 401)
})
