/**
 * Google Calendar OAuth routes — calendar-module.
 * Mounted at /api/auth/google-calendar (via router.ts).
 *
 * GET /start      Build consent URL, store state nonce in KV, redirect user
 * GET /callback   Validate state, exchange code, encrypt tokens, upsert connection
 */
import { Hono } from 'hono'
import type { Context } from 'hono'
import type { AppEnv } from '../../types'
import { authMiddleware } from '../../middleware/auth'
import { upsertCalendarConnection } from '@zync/db/queries'
import {
  buildGoogleOAuthUrl,
  exchangeGoogleCode,
} from '@zync/calendar'
import { encryptToken } from '@zync/calendar/server'

export const oauthGoogleRoute = new Hono<AppEnv>()
oauthGoogleRoute.use('/init', authMiddleware)
oauthGoogleRoute.use('/start', authMiddleware)

const NONCE_TTL_SECONDS = 600

// ── GET /start ────────────────────────────────────────────────────────────────

async function handleInit(c: Context<AppEnv>) {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const clientId = c.env.GOOGLE_OAUTH_CLIENT_ID
  if (!clientId) {
    return c.json({ error: 'Google Calendar integration not configured' }, 500)
  }

  // Generate a cryptographically random state nonce
  const nonce = crypto.randomUUID()
  const statePayload = JSON.stringify({ nonce, userId: session.sub, tenantId: session.tid })
  const state = btoa(statePayload)

  // Store nonce in KV with TTL
  const kv = c.env.KV
  if (!kv) {
    return c.json({ error: 'KV not configured' }, 500)
  }
  await kv.put(`oauth_state:google:${nonce}`, statePayload, { expirationTtl: NONCE_TTL_SECONDS })

  const redirectUri = `${new URL(c.req.url).origin}/api/auth/google-calendar/callback`
  const url = buildGoogleOAuthUrl(clientId, redirectUri, state)

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

oauthGoogleRoute.get('/init', handleInit)
oauthGoogleRoute.get('/start', handleInit)

// ── GET /callback ─────────────────────────────────────────────────────────────

oauthGoogleRoute.get('/callback', async (c) => {
  const url = new URL(c.req.url)
  const code = url.searchParams.get('code')
  const stateParam = url.searchParams.get('state')
  const errorParam = url.searchParams.get('error')

  if (errorParam) {
    return c.redirect(`/settings/integrations/calendar?error=${encodeURIComponent(errorParam)}`, 302)
  }

  if (!code || !stateParam) {
    return c.json({ error: 'Missing code or state' }, 400)
  }

  let statePayload: { nonce: string; userId: string; tenantId: string }
  try {
    statePayload = JSON.parse(atob(stateParam)) as typeof statePayload
  } catch {
    return c.json({ error: 'Invalid state' }, 400)
  }

  const kv = c.env.KV
  if (!kv) {
    return c.json({ error: 'KV not configured' }, 500)
  }

  // Validate nonce (consume it — one-time use)
  const stored = await kv.get(`oauth_state:google:${statePayload.nonce}`)
  if (!stored) {
    return c.json({ error: 'State expired or invalid' }, 400)
  }
  await kv.delete(`oauth_state:google:${statePayload.nonce}`)

  // The KV-stored payload is authoritative — it was written server-side at /start
  // from the authenticated session. The URL state param is attacker-controllable;
  // never trust its userId/tenantId for the connection binding.
  let verified: { nonce: string; userId: string; tenantId: string }
  try {
    verified = JSON.parse(stored) as typeof verified
  } catch {
    return c.json({ error: 'Corrupt state' }, 400)
  }
  if (verified.nonce !== statePayload.nonce) {
    return c.json({ error: 'State mismatch' }, 400)
  }

  const clientId = c.env.GOOGLE_OAUTH_CLIENT_ID
  const clientSecret = c.env.GOOGLE_OAUTH_CLIENT_SECRET
  const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY

  if (!clientId || !clientSecret || !encryptionKey) {
    return c.json({ error: 'Google Calendar integration not configured' }, 500)
  }

  const redirectUri = `${url.origin}/api/auth/google-calendar/callback`

  try {
    const tokens = await exchangeGoogleCode(code, clientId, clientSecret, redirectUri)
    const connectedEmail = await fetchGoogleEmail(tokens.accessToken)

    const [encryptedAccess, encryptedRefresh] = await Promise.all([
      encryptToken(tokens.accessToken, encryptionKey),
      encryptToken(tokens.refreshToken, encryptionKey),
    ])

    const db = c.get('db')
    await upsertCalendarConnection(db, {
      tenantId: verified.tenantId,
      userId: verified.userId,
      provider: 'google',
      externalUserId: tokens.sub,
      connectedEmail,
      accessToken: encryptedAccess,
      refreshToken: encryptedRefresh,
      tokenExpiresAt: new Date(tokens.expiresAt),
      syncDirection: 'two_way',
      syncTaskDueDates: true,
      syncManualEvents: true,
      syncCustomerMeetings: false,
      syncEnabled: true,
      status: 'active',
      lastSyncError: null,
    })

    return c.redirect('/settings/integrations/calendar?connected=google', 302)
  } catch (e) {
    const msg = e instanceof Error ? e.message : 'Unknown error'
    return c.redirect(`/settings/integrations/calendar?error=${encodeURIComponent(msg)}`, 302)
  }
})

async function fetchGoogleEmail(accessToken: string): Promise<string | null> {
  const res = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
    headers: { Authorization: `Bearer ${accessToken}` },
  })
  if (!res.ok) {
    return null
  }

  const body = (await res.json()) as { email?: string | null }
  return body.email ?? null
}
