/**
 * Calendar connections routes — calendar-module.
 * Mounted at /api/calendar (via router.ts).
 *
 * GET    /connections           list the session user's calendar connections
 * DELETE /connections/:id       delete a connection (unregisters watch channel)
 * GET    /connections/calendars list provider calendars for a connection
 * PATCH  /connections/:id       update selectedCalendarId / syncEnabled
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import { requirePermission } from '../../middleware/guards'
import {
  listCalendarConnections,
  getCalendarConnection,
  deleteCalendarConnection,
  updateCalendarConnection,
} from '@zync/db/queries'
import { GoogleCalendarProvider, OutlookCalendarProvider } from '@zync/calendar'
import { decryptToken } from '@zync/calendar/server'
import type { CalendarConnectionObject } from '@zync/calendar'
import { outlookClientStateKey } from '../../lib/outlook-calendar-webhook'

export const calendarConnectionsRoute = new Hono<AppEnv>()

const updateConnectionSchema = z.object({
  selectedCalendarId: z.string().min(1).nullable().optional(),
  syncEnabled: z.boolean().optional(),
})

function serializeConnection(row: {
  id: string
  tenantId: string
  userId: string
  provider: string
  externalUserId: string
  selectedCalendarId: string | null
  syncEnabled: boolean
  lastSyncedAt: Date | null
  createdAt: Date
}): CalendarConnectionObject {
  return {
    id: row.id,
    tenantId: row.tenantId,
    userId: row.userId,
    provider: row.provider as 'google' | 'outlook',
    externalUserId: row.externalUserId,
    selectedCalendarId: row.selectedCalendarId ?? null,
    syncEnabled: row.syncEnabled,
    lastSyncedAt: row.lastSyncedAt ? row.lastSyncedAt.toISOString() : null,
    createdAt: row.createdAt.toISOString(),
  }
}

// ── GET /connections ──────────────────────────────────────────────────────────

calendarConnectionsRoute.get('/connections', 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 rows = await listCalendarConnections(db, session.tid, session.sub)
  return c.json({ connections: rows.map(serializeConnection) }, 200)
})

// ── DELETE /connections/:id ───────────────────────────────────────────────────

calendarConnectionsRoute.delete(
  '/connections/:id',
  requirePermission('calendar:connect'),
  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 id = c.req.param('id')

    // Ownership check: only the owning user can delete their connection
    const conn = await getCalendarConnection(db, session.tid, id)
    if (!conn) return c.json({ error: 'Not found' }, 404)
    if (conn.userId !== session.sub) {
      return c.json({ error: 'Forbidden' }, 403)
    }

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

    // Best-effort: unregister watch channel — provider-specific, fire and forget
    // (The sync cron will naturally stop picking this connection up)

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

calendarConnectionsRoute.get('/connections/:id/calendars', requirePermission('calendar:connect'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY
  if (!encryptionKey) {
    return c.json({ error: 'Encryption key not configured' }, 500)
  }

  const db = c.get('db')
  const conn = await getCalendarConnection(db, session.tid, c.req.param('id'))
  if (!conn) return c.json({ error: 'Not found' }, 404)
  if (conn.userId !== session.sub) return c.json({ error: 'Forbidden' }, 403)

  const accessToken = await decryptToken(conn.accessToken, encryptionKey)
  const provider =
    conn.provider === 'google'
      ? new GoogleCalendarProvider(
          c.env.GOOGLE_OAUTH_CLIENT_ID ?? '',
          c.env.GOOGLE_OAUTH_CLIENT_SECRET ?? '',
        )
      : new OutlookCalendarProvider(
          c.env.MICROSOFT_OAUTH_CLIENT_ID ?? '',
          c.env.MICROSOFT_OAUTH_CLIENT_SECRET ?? '',
        )

  const calendars = await provider.listCalendars(accessToken)
  return c.json({ calendars }, 200)
})

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

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

  const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY
  if (!encryptionKey) {
    return c.json({ error: 'Encryption key not configured' }, 500)
  }

  const db = c.get('db')
  const conn = await getCalendarConnection(db, session.tid, c.req.param('id'))
  if (!conn) return c.json({ error: 'Not found' }, 404)
  if (conn.userId !== session.sub) return c.json({ error: 'Forbidden' }, 403)

  const updated = await updateCalendarConnection(db, session.tid, conn.id, {
    selectedCalendarId:
      parsed.data.selectedCalendarId === undefined ? undefined : parsed.data.selectedCalendarId,
    syncEnabled: parsed.data.syncEnabled,
  })
  if (!updated) return c.json({ error: 'Not found' }, 404)

  if (updated.syncEnabled && updated.selectedCalendarId) {
    const accessToken = await decryptToken(conn.accessToken, encryptionKey)

    if (updated.provider === 'google') {
      const clientId = c.env.GOOGLE_OAUTH_CLIENT_ID
      const clientSecret = c.env.GOOGLE_OAUTH_CLIENT_SECRET
      const kv = c.env.KV
      if (clientId && clientSecret && kv) {
        const provider = new GoogleCalendarProvider(clientId, clientSecret)
        const channelId = crypto.randomUUID()
        const callbackUrl = `${new URL(c.req.url).origin}/api/webhooks/calendar/google/${updated.id}`
        const watch = await provider.registerWatch(accessToken, updated.selectedCalendarId, callbackUrl, {
          channelId,
        })
        const storedChannelId = watch.channelId ?? channelId
        await kv.put(`google_channel:${storedChannelId}`, updated.id)
      }
    } else {
      const clientId = c.env.MICROSOFT_OAUTH_CLIENT_ID
      const clientSecret = c.env.MICROSOFT_OAUTH_CLIENT_SECRET
      const kv = c.env.KV
      if (clientId && clientSecret && kv) {
        const provider = new OutlookCalendarProvider(clientId, clientSecret)
        const clientState = crypto.randomUUID()
        const callbackUrl = `${new URL(c.req.url).origin}/api/webhooks/calendar/outlook/${updated.id}`
        await kv.put(outlookClientStateKey(updated.id), clientState)
        await provider.registerWatch(accessToken, updated.selectedCalendarId, callbackUrl, {
          clientState,
        })
      }
    }
  }

  return c.json({ connection: serializeConnection(updated) }, 200)
})
