import { Router } from 'express'
import rateLimit from 'express-rate-limit'
import { z } from 'zod'
import { env, googleOAuthEnabled } from '../config/env.js'
import { asyncHandler } from '../middleware/asyncHandler.js'
import { requireAuth } from '../middleware/auth.js'
import { validateBody } from '../middleware/validate.js'
import { unauthorized } from '../lib/errors.js'
import { readRefreshToken } from '../lib/tokens.js'
import * as authService from '../services/authService.js'

const REFRESH_COOKIE = 'refreshToken'

const credentials = z.object({
  email: z.email('Enter a valid email'),
  password: z.string().min(8, 'Password must be at least 8 characters').max(72),
})

const signupSchema = credentials.extend({
  name: z.string().trim().min(1, 'Name is required').max(80),
})

const guestSchema = z.object({
  name: z.string().trim().min(1, 'Name is required').max(80),
})

const cookieOptions = {
  httpOnly: true,
  sameSite: 'lax' as const,
  secure: env.NODE_ENV === 'production',
  path: '/api/auth',
  maxAge: 7 * 24 * 60 * 60 * 1000,
}

export const authRouter = Router()

if (env.AUTH_RATE_LIMIT_MAX > 0) {
  authRouter.use(
    rateLimit({
      windowMs: env.AUTH_RATE_LIMIT_WINDOW_MS,
      limit: env.AUTH_RATE_LIMIT_MAX,
      standardHeaders: true,
      legacyHeaders: false,
    }),
  )
}

authRouter.post(
  '/signup',
  validateBody(signupSchema),
  asyncHandler(async (req, res) => {
    const session = await authService.signup(req.body)
    res.cookie(REFRESH_COOKIE, session.refreshToken, cookieOptions)
    res.status(201).json({ accessToken: session.accessToken, user: session.user })
  }),
)

authRouter.post(
  '/login',
  validateBody(credentials),
  asyncHandler(async (req, res) => {
    const session = await authService.login(req.body)
    res.cookie(REFRESH_COOKIE, session.refreshToken, cookieOptions)
    res.json({ accessToken: session.accessToken, user: session.user })
  }),
)

authRouter.post(
  '/guest',
  validateBody(guestSchema),
  asyncHandler(async (req, res) => {
    const session = authService.guestSession(req.body.name)
    res.json({ accessToken: session.accessToken, user: { id: session.identity.sub, name: session.identity.name, kind: 'guest' } })
  }),
)

authRouter.post(
  '/refresh',
  asyncHandler(async (req, res) => {
    const token = req.cookies?.[REFRESH_COOKIE]
    if (typeof token !== 'string' || token.length === 0) throw unauthorized('Missing refresh token')
    const payload = readRefreshToken(token)
    if (payload.kind !== 'user') throw unauthorized('Guests cannot refresh')
    const session = await authService.refreshSession(payload.sub, token)
    res.cookie(REFRESH_COOKIE, session.refreshToken, cookieOptions)
    res.json({ accessToken: session.accessToken, user: session.user })
  }),
)

authRouter.post(
  '/logout',
  requireAuth,
  asyncHandler(async (req, res) => {
    if (req.identity?.kind === 'user') await authService.logout(req.identity.sub)
    res.clearCookie(REFRESH_COOKIE, { path: '/api/auth' })
    res.status(204).end()
  }),
)

authRouter.get(
  '/me',
  requireAuth,
  asyncHandler(async (req, res) => {
    const user = await authService.currentUser(req.identity!)
    res.json({ user })
  }),
)

authRouter.get('/google', (_req, res) => {
  if (!googleOAuthEnabled) {
    res.status(404).json({ error: { code: 'NOT_CONFIGURED', message: 'Google login is not configured' } })
    return
  }
  const url = new URL('https://accounts.google.com/o/oauth2/v2/auth')
  url.searchParams.set('client_id', env.GOOGLE_CLIENT_ID!)
  url.searchParams.set('redirect_uri', env.GOOGLE_REDIRECT_URI!)
  url.searchParams.set('response_type', 'code')
  url.searchParams.set('scope', 'openid email profile')
  url.searchParams.set('prompt', 'select_account')
  res.redirect(url.toString())
})

authRouter.get(
  '/google/callback',
  asyncHandler(async (req, res) => {
    if (!googleOAuthEnabled) throw unauthorized('Google login is not configured')
    const code = typeof req.query.code === 'string' ? req.query.code : ''
    if (!code) throw unauthorized('Missing authorization code')

    const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        code,
        client_id: env.GOOGLE_CLIENT_ID!,
        client_secret: env.GOOGLE_CLIENT_SECRET!,
        redirect_uri: env.GOOGLE_REDIRECT_URI!,
        grant_type: 'authorization_code',
      }),
    })
    if (!tokenResponse.ok) throw unauthorized('Google token exchange failed')
    const tokenJson = (await tokenResponse.json()) as { access_token?: string }
    if (!tokenJson.access_token) throw unauthorized('Google token exchange failed')

    const profileResponse = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
      headers: { authorization: `Bearer ${tokenJson.access_token}` },
    })
    if (!profileResponse.ok) throw unauthorized('Could not read Google profile')
    const profile = (await profileResponse.json()) as { sub?: string; email?: string; name?: string; picture?: string }
    if (!profile.sub || !profile.email) throw unauthorized('Google profile is missing an email')

    const session = await authService.upsertGoogleUser({
      sub: profile.sub,
      email: profile.email,
      name: profile.name || profile.email,
      picture: profile.picture,
    })
    res.cookie(REFRESH_COOKIE, session.refreshToken, cookieOptions)
    const next = new URL('/auth/callback', env.CLIENT_ORIGIN)
    next.searchParams.set('accessToken', session.accessToken)
    res.redirect(next.toString())
  }),
)
