import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import { authMiddleware } from '../../middleware/auth'
import {
  createDb,
  listCalendarSettingsConnections,
  updateCalendarConnectionPrefs,
  listCalendarConnections,
  deleteCalendarConnection,
} from '@zync/db/queries'
import { GoogleCalendarProvider, OutlookCalendarProvider } from '@zync/calendar'
import { decryptToken } from '@zync/calendar/server'

const providerSchema = z.enum(['google', 'outlook'])

const patchConnectionSchema = z.object({
  selected_calendar_id: z.string().min(1).nullable().optional(),
  selected_calendar_name: z.string().min(1).nullable().optional(),
  sync_direction: z.enum(['two_way', 'read_only', 'push_only']).optional(),
  sync_task_due_dates: z.boolean().optional(),
  sync_manual_events: z.boolean().optional(),
  sync_customer_meetings: z.boolean().optional(),
})

export const calendarSettingsRoute = new Hono<AppEnv>()

calendarSettingsRoute.use('*', authMiddleware)

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

  const db = createDb(c.env)
  const connections = await listCalendarSettingsConnections(db, session.tid, session.sub)
  return c.json(connections, 200)
})

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

  const providerParsed = providerSchema.safeParse(c.req.query('provider'))
  if (!providerParsed.success) {
    return c.json({ error: 'Invalid provider' }, 400)
  }

  const db = createDb(c.env)
  const connection = await findConnectionByProvider(db, session.tid, session.sub, providerParsed.data)
  if (!connection || connection.userId !== session.sub) {
    return c.json({ error: 'Connection not found' }, 404)
  }

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

  const accessToken = await decryptToken(connection.accessToken, encryptionKey)
  const provider =
    providerParsed.data === '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.map((calendar) => ({
      id: calendar.id,
      name: calendar.name,
      primary: calendar.isPrimary === true,
    })),
    200,
  )
})

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

    const providerParsed = providerSchema.safeParse(c.req.param('provider'))
    if (!providerParsed.success) {
      return c.json({ error: 'Invalid provider' }, 400)
    }

    const body = await c.req.json().catch(() => null)
    const parsed = patchConnectionSchema.safeParse(body)
    if (!parsed.success) {
      return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
    }

    const db = createDb(c.env)
    const updated = await updateCalendarConnectionPrefs(
      db,
      session.tid,
      session.sub,
      providerParsed.data,
      parsed.data,
    )
    if (!updated) {
      return c.json({ error: 'Connection not found' }, 404)
    }

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

calendarSettingsRoute.delete(
  '/connections/:provider',
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const providerParsed = providerSchema.safeParse(c.req.param('provider'))
    if (!providerParsed.success) {
      return c.json({ error: 'Invalid provider' }, 400)
    }

    const db = createDb(c.env)
    const existing = await findConnectionByProvider(db, session.tid, session.sub, providerParsed.data)
    if (!existing) {
      return new Response(null, { status: 204 })
    }

    const encryptionKey = c.env.INTEGRATION_ENCRYPTION_KEY
    if (encryptionKey) {
      const refreshToken = await decryptToken(existing.refreshToken, encryptionKey)
      await revokeProviderToken(existing.provider as 'google' | 'outlook', refreshToken)
    }

    await deleteCalendarConnection(db, session.tid, existing.id)
    return new Response(null, { status: 204 })
  },
)

async function findConnectionByProvider(
  db: ReturnType<typeof createDb>,
  tenantId: string,
  userId: string,
  provider: 'google' | 'outlook',
) {
  const rows = await listCalendarConnections(db, tenantId, userId)
  return rows.find((row) => row.provider === provider)
}

async function revokeProviderToken(provider: 'google' | 'outlook', refreshToken: string): Promise<void> {
  try {
    if (provider === 'google') {
      await fetch(`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(refreshToken)}`, {
        method: 'POST',
      })
    }
  } catch {
    return
  }
}
