/**
 * Calendar sync cron — calendar-module.
 * Mounted at /api/cron/calendar-sync.
 *
 * POST /api/cron/calendar-sync
 *   Iterates all sync_enabled calendar_connections, fetches changes since last_synced_at,
 *   upserts into calendar_events, advances last_synced_at.
 *   Protected: only callable by Cloudflare cron trigger (CF-Worker-Cron header) or INTERNAL_CRON_SECRET.
 */
import { Hono } from 'hono'
import { timingSafeEqual } from '@zync/auth'
import type { AppEnv } from '../../types'
import {
  listSyncEnabledConnections,
  upsertExternalCalendarEvent,
  updateCalendarConnectionLastSynced,
  updateCalendarConnectionTokens,
} from '@zync/db/queries'
import {
  GoogleCalendarProvider,
  OutlookCalendarProvider,
} from '@zync/calendar'
import { decryptToken, encryptToken } from '@zync/calendar/server'
import type { ExternalEvent } from '@zync/calendar'

export const cronSyncRoute = new Hono<AppEnv>()

const REFRESH_SKEW_MS = 5 * 60 * 1000

cronSyncRoute.post('/cron/calendar-sync', async (c) => {
  // Timing-safe secret check — fail CLOSED when secret unset/weak
  const secret =
    c.req.header('x-cron-secret') ??
    c.req.header('authorization')?.replace('Bearer ', '') ??
    ''
  const expected = c.env.CRON_SECRET
  if (!expected || expected.length < 32) {
    return c.json({ error: 'Server misconfigured' }, 500)
  }
  if (!secret || !timingSafeEqual(secret, expected)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY
  const googleClientId = c.env.GOOGLE_OAUTH_CLIENT_ID
  const googleClientSecret = c.env.GOOGLE_OAUTH_CLIENT_SECRET
  const outlookClientId = c.env.MICROSOFT_OAUTH_CLIENT_ID
  const outlookClientSecret = c.env.MICROSOFT_OAUTH_CLIENT_SECRET

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

  const db = c.get('db')
  const connections = await listSyncEnabledConnections(db)

  const results: Array<{ id: string; provider: string; status: 'ok' | 'error'; error?: string }> = []

  for (const conn of connections) {
    try {
      const [accessTokenPlain, refreshTokenPlain] = await Promise.all([
        decryptToken(conn.accessToken, encryptionKey),
        decryptToken(conn.refreshToken, encryptionKey),
      ])

      if (!conn.selectedCalendarId) {
        results.push({ id: conn.id, provider: conn.provider, status: 'ok' })
        continue
      }

      let currentAccessToken = accessTokenPlain
      let changes: ExternalEvent[] = []

      if (conn.provider === 'google' && googleClientId && googleClientSecret) {
        const provider = new GoogleCalendarProvider(googleClientId, googleClientSecret)

        if (conn.tokenExpiresAt && conn.tokenExpiresAt.getTime() - Date.now() < REFRESH_SKEW_MS) {
          const refreshed = await provider.refreshAccessToken(refreshTokenPlain)
          const newEncrypted = await encryptToken(refreshed.accessToken, encryptionKey)
          await updateCalendarConnectionTokens(db, conn.tenantId, conn.id, newEncrypted, new Date(refreshed.expiresAt))
          currentAccessToken = refreshed.accessToken
        }

        changes = await provider.fetchChanges(
          currentAccessToken,
          conn.selectedCalendarId,
          conn.lastSyncedAt?.toISOString() ?? null,
        )
      } else if (conn.provider === 'outlook' && outlookClientId && outlookClientSecret) {
        const provider = new OutlookCalendarProvider(outlookClientId, outlookClientSecret)

        if (conn.tokenExpiresAt && conn.tokenExpiresAt.getTime() - Date.now() < REFRESH_SKEW_MS) {
          const refreshed = await provider.refreshAccessToken(refreshTokenPlain)
          const newEncrypted = await encryptToken(refreshed.accessToken, encryptionKey)
          await updateCalendarConnectionTokens(db, conn.tenantId, conn.id, newEncrypted, new Date(refreshed.expiresAt))
          currentAccessToken = refreshed.accessToken
        }

        changes = await provider.fetchChanges(
          currentAccessToken,
          conn.selectedCalendarId,
          conn.lastSyncedAt?.toISOString() ?? null,
        )
      }

      // Upsert changed events
      for (const ev of changes) {
        await upsertExternalCalendarEvent(db, {
          tenantId: conn.tenantId,
          createdBy: conn.userId,
          title: ev.title,
          description: ev.description,
          startAt: new Date(ev.startAt),
          endAt: new Date(ev.endAt),
          allDay: ev.allDay,
          location: ev.location,
          source: conn.provider as 'google' | 'outlook',
          externalId: ev.externalId,
          externalCalendarId: conn.selectedCalendarId,
          syncedAt: new Date(),
          syncStatus: 'synced',
        })
      }

      await updateCalendarConnectionLastSynced(db, conn.tenantId, conn.id)
      results.push({ id: conn.id, provider: conn.provider, status: 'ok' })
    } catch (e) {
      results.push({
        id: conn.id,
        provider: conn.provider,
        status: 'error',
        error: e instanceof Error ? e.message : String(e),
      })
    }
  }

  const ok = results.filter((r) => r.status === 'ok').length
  const errors = results.filter((r) => r.status === 'error').length

  return c.json({ synced: ok, errors, results }, 200)
})
