/**
 * Calendar events CRUD routes — calendar-module.
 * Mounted at /api/calendar (via router.ts).
 *
 * GET    /events            list events in a date range (manual + task-derived)
 * POST   /events            create a manual event
 * GET    /events/:id        get a single event
 * PATCH  /events/:id        update (rejects readOnly external events)
 * DELETE /events/:id        delete (enqueues provider delete for synced events)
 */
import { Hono } from 'hono'
import type { AppEnv } from '../../types'
import { requirePermission } from '../../middleware/guards'
import {
  listCalendarEvents,
  listTasksDueInRange,
  listProjectMilestonesDueInRange,
  getCalendarEvent,
  createCalendarEvent,
  updateCalendarEvent,
  deleteCalendarEvent,
  assertTenantOwnsCustomer,
  assertTenantOwnsProject,
  invalidTenantReferenceBody,
} from '@zync/db/queries'
import { serializeCalendarEvent, taskToCalendarEvent, parseRange, RangeError } from '@zync/calendar'
import { createCalendarEventSchema, updateCalendarEventSchema, rangeQuerySchema } from './validation'

export const calendarEventsRoute = new Hono<AppEnv>()

// ── GET /events ───────────────────────────────────────────────────────────────

calendarEventsRoute.get('/events', requirePermission('calendar:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const url = new URL(c.req.url)
  const rawStart = url.searchParams.get('start')
  const rawEnd = url.searchParams.get('end')

  if (!rawStart || !rawEnd) {
    return c.json({ error: 'start and end query params are required' }, 400)
  }

  const rangeResult = rangeQuerySchema.safeParse({ start: rawStart, end: rawEnd })
  if (!rangeResult.success) {
    return c.json({ error: 'Invalid range', issues: rangeResult.error.issues }, 400)
  }

  let range: { start: Date; end: Date }
  try {
    range = parseRange(rawStart, rawEnd)
  } catch (e) {
    if (e instanceof RangeError) {
      return c.json({ error: e.message }, 400)
    }
    throw e
  }

  const db = c.get('db')
  const tenantId = session.tid

  const [eventRows, taskRows, milestoneRows] = await Promise.all([
    listCalendarEvents(db, tenantId, range.start, range.end),
    listTasksDueInRange(db, tenantId, range.start, range.end),
    listProjectMilestonesDueInRange(db, tenantId, range.start, range.end),
  ] as const)

  const calendarEventObjects = eventRows.map(serializeCalendarEvent)
  const taskEventObjects = taskRows.map(taskToCalendarEvent)
  const milestoneEventObjects = milestoneRows.map((row) => ({
    id: `project-milestone:${row.id}`,
    tenantId: row.tenantId,
    createdBy: '',
    title: row.title,
    description: null,
    startAt: `${row.dueDate}T00:00:00.000Z`,
    endAt: `${row.dueDate}T00:00:00.000Z`,
    allDay: true,
    location: null,
    source: 'project' as const,
    taskId: null,
    projectId: row.projectId,
    customerId: null,
    externalId: null,
    externalCalendarId: null,
    syncedAt: null,
    syncStatus: 'local' as const,
    readOnly: false,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
  }))
  const all = [...calendarEventObjects, ...taskEventObjects, ...milestoneEventObjects].sort(
    (a, b) => new Date(a.startAt).getTime() - new Date(b.startAt).getTime(),
  )

  return c.json({ events: all }, 200)
})

// ── POST /events ──────────────────────────────────────────────────────────────

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

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

  const db = c.get('db')
  const tenantId = session.tid
  const { title, description, startAt, endAt, allDay, location, customerId, projectId } = parsed.data

  if (!(await assertTenantOwnsCustomer(db, tenantId, customerId ?? null))) {
    return c.json(invalidTenantReferenceBody('customerId'), 400)
  }
  if (!(await assertTenantOwnsProject(db, tenantId, projectId ?? null))) {
    return c.json(invalidTenantReferenceBody('projectId'), 400)
  }

  const row = await createCalendarEvent(db, {
    tenantId,
    createdBy: session.sub,
    title,
    description: description ?? null,
    startAt: new Date(startAt),
    endAt: new Date(endAt),
    allDay,
    location: location ?? null,
    source: 'manual',
    customerId: customerId ?? null,
    projectId: projectId ?? null,
    syncStatus: 'local',
  })

  // Best-effort: enqueue a push job for synced connections
  const env = c.env
  if (env.QUEUE) {
    void env.QUEUE.send({
      type: 'calendar.event.push',
      tenantId,
      userId: session.sub,
      eventId: row.id,
    }).catch(() => {/* non-fatal */})
  }

  return c.json(serializeCalendarEvent(row), 201)
})

// ── GET /events/:id ───────────────────────────────────────────────────────────

calendarEventsRoute.get('/events/:id', requirePermission('calendar: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 row = await getCalendarEvent(db, session.tid, c.req.param('id'))
  if (!row) return c.json({ error: 'Not found' }, 404)

  return c.json(serializeCalendarEvent(row), 200)
})

// ── PATCH /events/:id ─────────────────────────────────────────────────────────

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

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

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

  // Reject edits to readOnly external-only events
  const externalSources = ['google', 'outlook']
  if (externalSources.includes(existing.source) && existing.externalId != null) {
    return c.json(
      { error: 'This event is managed by an external calendar and cannot be edited in Zync.' },
      409,
    )
  }

  if (parsed.data.customerId !== undefined) {
    if (!(await assertTenantOwnsCustomer(db, session.tid, parsed.data.customerId))) {
      return c.json(invalidTenantReferenceBody('customerId'), 400)
    }
  }
  if (parsed.data.projectId !== undefined) {
    if (!(await assertTenantOwnsProject(db, session.tid, parsed.data.projectId))) {
      return c.json(invalidTenantReferenceBody('projectId'), 400)
    }
  }

  const updateData: Record<string, unknown> = {}
  const d = parsed.data
  if (d.title !== undefined) updateData.title = d.title
  if (d.description !== undefined) updateData.description = d.description
  if (d.startAt !== undefined) updateData.startAt = new Date(d.startAt)
  if (d.endAt !== undefined) updateData.endAt = new Date(d.endAt)
  if (d.allDay !== undefined) updateData.allDay = d.allDay
  if (d.location !== undefined) updateData.location = d.location
  if (d.customerId !== undefined) updateData.customerId = d.customerId
  if (d.projectId !== undefined) updateData.projectId = d.projectId

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

  // Enqueue provider update if the event is synced
  if (existing.syncStatus === 'synced' && existing.externalId) {
    const env = c.env
    if (env.QUEUE) {
      void env.QUEUE.send({
        type: 'calendar.event.update',
        tenantId: session.tid,
        userId: session.sub,
        eventId: updated.id,
      }).catch(() => {/* non-fatal */})
    }
  }

  return c.json(serializeCalendarEvent(updated), 200)
})

// ── DELETE /events/:id ────────────────────────────────────────────────────────

calendarEventsRoute.delete('/events/:id', requirePermission('calendar:write'), 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 existing = await getCalendarEvent(db, session.tid, c.req.param('id'))
  if (!existing) return c.json({ error: 'Not found' }, 404)

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

  // Enqueue provider delete if the event was synced
  if (existing.syncStatus === 'synced' && existing.externalId) {
    const env = c.env
    if (env.QUEUE) {
      void env.QUEUE.send({
        type: 'calendar.event.delete',
        tenantId: session.tid,
        userId: session.sub,
        externalId: existing.externalId,
        externalCalendarId: existing.externalCalendarId,
        source: existing.source,
      }).catch(() => {/* non-fatal */})
    }
  }

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