/**
 * OAuth Routes
 * Gmail and Outlook OAuth2 authentication
 */

import express from 'express'
import { OAuth2Client } from 'google-auth-library'
import { ConfidentialClientApplication } from '@azure/msal-node'
import { asyncHandler, ValidationError, AppError, AuthorizationError } from '../middleware/errorHandler.mjs'
import { respond } from '../utils/response.mjs'
import { emailAccounts } from '../services/database.mjs'
import { config, features } from '../config/index.mjs'
import { log } from '../middleware/logger.mjs'
import { requireAuth } from '../middleware/auth.mjs'
import { buildOAuthState, parseOAuthState } from '../utils/oauth-state.mjs'

const router = express.Router()

function requireAuthUnlessOAuthPublic(req, res, next) {
  const p = req.path || ''
  if (['/gmail/callback', '/outlook/callback', '/status'].includes(p)) {
    return next()
  }
  return requireAuth(req, res, next)
}

router.use(requireAuthUnlessOAuthPublic)

// Google OAuth client (lazy initialization)
let googleClient = null

function getGoogleClient() {
  if (!googleClient && features.gmail()) {
    googleClient = new OAuth2Client(
      config.google.clientId,
      config.google.clientSecret,
      config.google.redirectUri
    )
  }
  return googleClient
}

// Microsoft OAuth client (lazy initialization)
let msalClient = null

function getMsalClient() {
  if (!msalClient && features.outlook()) {
    msalClient = new ConfidentialClientApplication({
      auth: {
        clientId: config.microsoft.clientId,
        clientSecret: config.microsoft.clientSecret,
        authority: 'https://login.microsoftonline.com/common',
      },
    })
  }
  return msalClient
}

/**
 * Gmail OAuth scopes
 */
const GMAIL_SCOPES = [
  'https://www.googleapis.com/auth/gmail.modify',
  'https://www.googleapis.com/auth/gmail.labels',
  'https://www.googleapis.com/auth/userinfo.email',
]

/**
 * Microsoft Graph scopes
 */
const OUTLOOK_SCOPES = [
  'Mail.ReadWrite',
  'User.Read',
  'offline_access',
]

// ═══════════════════════════════════════════════════════════════════════════
// GMAIL OAUTH
// ═══════════════════════════════════════════════════════════════════════════

/**
 * GET /api/oauth/gmail/connect
 * Initiate Gmail OAuth flow
 */
router.get('/gmail/connect', asyncHandler(async (req, res) => {
  if (!features.gmail()) {
    throw new AppError('Gmail OAuth ist nicht konfiguriert', 503, 'FEATURE_DISABLED')
  }

  const client = getGoogleClient()
  const authUrl = client.generateAuthUrl({
    access_type: 'offline',
    scope: GMAIL_SCOPES,
    prompt: 'consent',
    state: buildOAuthState(req.appwriteUser.id),
    include_granted_scopes: true,
  })

  respond.success(res, { url: authUrl })
}))

/**
 * GET /api/oauth/gmail/callback
 * Gmail OAuth callback
 */
router.get('/gmail/callback', asyncHandler(async (req, res) => {
  const { code, state, error, error_description } = req.query

  log.info('Gmail OAuth Callback erhalten', { 
    hasCode: !!code, 
    hasState: !!state, 
    error: error || 'none' 
  })

  if (error) {
    log.warn('Gmail OAuth abgelehnt', { error, error_description })
    return res.redirect(`${config.frontendUrl}/settings?error=oauth_denied&message=${encodeURIComponent(error_description || error)}`)
  }

  if (!code || !state) {
    log.error('Gmail OAuth: Code oder State fehlt')
    return res.redirect(`${config.frontendUrl}/settings?error=missing_params`)
  }

  let userId
  try {
    const stateData = parseOAuthState(state)
    userId = stateData.userId
  } catch (e) {
    log.error('Gmail OAuth: State konnte nicht geparst werden', { state, error: e.message })
    return res.redirect(`${config.frontendUrl}/settings?error=invalid_state`)
  }

  // Create a FRESH OAuth client for this request
  const client = new OAuth2Client(
    config.google.clientId,
    config.google.clientSecret,
    config.google.redirectUri
  )

  try {
    log.info('Gmail OAuth: Tausche Code gegen Token...')
    
    // Exchange code for tokens
    const { tokens } = await client.getToken(code)
    
    log.info('Gmail OAuth: Token erhalten', { 
      hasAccessToken: !!tokens.access_token,
      hasRefreshToken: !!tokens.refresh_token,
      expiresIn: tokens.expiry_date 
    })
    
    client.setCredentials(tokens)

    // Get user email
    const { google } = await import('googleapis')
    const oauth2 = google.oauth2({ version: 'v2', auth: client })
    const { data: userInfo } = await oauth2.userinfo.get()

    log.info('Gmail OAuth: User Info erhalten', { email: userInfo.email })

    // Check if account already exists
    const existingAccounts = await emailAccounts.getByUser(userId)
    const alreadyConnected = existingAccounts.find(a => a.email === userInfo.email)

    if (alreadyConnected) {
      // Update existing account with new tokens
      const { db, Collections } = await import('../services/database.mjs')
      await db.update(Collections.EMAIL_ACCOUNTS, alreadyConnected.$id, {
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token || alreadyConnected.refreshToken,
        expiresAt: tokens.expiry_date || 0,
        isActive: true,
      })
      log.success(`Gmail aktualisiert: ${userInfo.email}`)
    } else {
      // Save new account to database
      await emailAccounts.create({
        userId,
        provider: 'gmail',
        email: userInfo.email,
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token || '',
        expiresAt: tokens.expiry_date || 0,
        isActive: true,
      })
      log.success(`Gmail verbunden: ${userInfo.email}`)
    }

    res.redirect(`${config.frontendUrl}/settings?gmail=connected&email=${encodeURIComponent(userInfo.email)}`)
  } catch (tokenError) {
    log.error('Gmail OAuth Token-Fehler', { 
      error: tokenError.message,
      code: tokenError.code,
      response: tokenError.response?.data 
    })
    
    // Provide more specific error messages
    let errorMessage = 'token_error'
    if (tokenError.message.includes('invalid_grant')) {
      errorMessage = 'invalid_grant'
    } else if (tokenError.message.includes('invalid_client')) {
      errorMessage = 'invalid_client'
    }
    
    res.redirect(`${config.frontendUrl}/settings?error=${errorMessage}&details=${encodeURIComponent(tokenError.message)}`)
  }
}))

/**
 * POST /api/oauth/gmail/refresh
 * Refresh Gmail access token
 */
router.post('/gmail/refresh', asyncHandler(async (req, res) => {
  const { accountId } = req.body

  if (!accountId) {
    throw new ValidationError('accountId ist erforderlich')
  }

  const account = await emailAccounts.get(accountId)

  if (account.userId !== req.appwriteUser.id) {
    throw new AuthorizationError('No permission for this account')
  }

  if (account.provider !== 'gmail') {
    throw new ValidationError('Kein Gmail-Konto')
  }

  if (!account.refreshToken) {
    throw new AppError('Kein Refresh Token verfügbar. Konto erneut verbinden.', 400, 'NO_REFRESH_TOKEN')
  }

  const client = getGoogleClient()
  client.setCredentials({ refresh_token: account.refreshToken })

  const { credentials } = await client.refreshAccessToken()

  // Update tokens
  const { db, Collections } = await import('../services/database.mjs')
  await db.update(Collections.EMAIL_ACCOUNTS, accountId, {
    accessToken: credentials.access_token,
    expiresAt: credentials.expiry_date || 0,
  })

  respond.success(res, {
    accessToken: credentials.access_token,
    expiresAt: credentials.expiry_date,
  })
}))

// ═══════════════════════════════════════════════════════════════════════════
// OUTLOOK OAUTH
// ═══════════════════════════════════════════════════════════════════════════

/**
 * GET /api/oauth/outlook/connect
 * Initiate Outlook OAuth flow
 */
router.get('/outlook/connect', asyncHandler(async (req, res) => {
  if (!features.outlook()) {
    throw new AppError('Outlook OAuth ist nicht konfiguriert', 503, 'FEATURE_DISABLED')
  }

  const client = getMsalClient()
  const authUrl = await client.getAuthCodeUrl({
    scopes: OUTLOOK_SCOPES,
    redirectUri: config.microsoft.redirectUri,
    state: buildOAuthState(req.appwriteUser.id),
    prompt: 'select_account',
  })

  respond.success(res, { url: authUrl })
}))

/**
 * GET /api/oauth/outlook/callback
 * Outlook OAuth callback
 */
router.get('/outlook/callback', asyncHandler(async (req, res) => {
  const { code, state, error, error_description } = req.query

  if (error) {
    log.warn('Outlook OAuth abgelehnt', { error, error_description })
    return respond.redirect(res, `${config.frontendUrl}/settings?error=oauth_denied`)
  }

  if (!code || !state) {
    throw new ValidationError('Code und State sind erforderlich')
  }

  let userId
  try {
    userId = parseOAuthState(state).userId
  } catch (e) {
    log.error('Outlook OAuth: invalid state', { error: e.message })
    return respond.redirect(res, `${config.frontendUrl}/settings?error=invalid_state`)
  }

  const client = getMsalClient()

  // Exchange code for tokens
  const tokenResponse = await client.acquireTokenByCode({
    code,
    scopes: OUTLOOK_SCOPES,
    redirectUri: config.microsoft.redirectUri,
  })

  // Get user email from Graph API
  const graphResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
    headers: {
      Authorization: `Bearer ${tokenResponse.accessToken}`,
    },
  })

  const userInfo = await graphResponse.json()

  // Save to database
  await emailAccounts.create({
    userId,
    provider: 'outlook',
    email: userInfo.mail || userInfo.userPrincipalName,
    accessToken: tokenResponse.accessToken,
    refreshToken: '', // MSAL handles token caching differently
    expiresAt: tokenResponse.expiresOn ? new Date(tokenResponse.expiresOn).getTime() : 0,
    isActive: true,
  })

  log.success(`Outlook verbunden: ${userInfo.mail || userInfo.userPrincipalName}`)

  respond.redirect(res, `${config.frontendUrl}/settings?outlook=connected`)
}))

/**
 * POST /api/oauth/outlook/refresh
 * Refresh Outlook access token
 */
router.post('/outlook/refresh', asyncHandler(async (req, res) => {
  const { accountId } = req.body

  if (!accountId) {
    throw new ValidationError('accountId ist erforderlich')
  }

  const account = await emailAccounts.get(accountId)

  if (account.userId !== req.appwriteUser.id) {
    throw new AuthorizationError('No permission for this account')
  }

  if (account.provider !== 'outlook') {
    throw new ValidationError('Kein Outlook-Konto')
  }

  const client = getMsalClient()

  // MSAL handles token refresh silently via cache
  const tokenResponse = await client.acquireTokenSilent({
    scopes: OUTLOOK_SCOPES,
    account: {
      homeAccountId: account.email,
    },
  }).catch(async () => {
    // If silent refresh fails, need to re-authenticate
    throw new AppError('Token abgelaufen. Konto erneut verbinden.', 401, 'TOKEN_EXPIRED')
  })

  // Update tokens
  const { db, Collections } = await import('../services/database.mjs')
  await db.update(Collections.EMAIL_ACCOUNTS, accountId, {
    accessToken: tokenResponse.accessToken,
    expiresAt: tokenResponse.expiresOn ? new Date(tokenResponse.expiresOn).getTime() : 0,
  })

  respond.success(res, {
    accessToken: tokenResponse.accessToken,
    expiresAt: tokenResponse.expiresOn,
  })
}))

// ═══════════════════════════════════════════════════════════════════════════
// STATUS CHECK
// ═══════════════════════════════════════════════════════════════════════════

/**
 * GET /api/oauth/status
 * Check which OAuth providers are configured
 */
router.get('/status', (req, res) => {
  respond.success(res, {
    gmail: {
      enabled: features.gmail(),
      scopes: features.gmail() ? GMAIL_SCOPES : [],
    },
    outlook: {
      enabled: features.outlook(),
      scopes: features.outlook() ? OUTLOOK_SCOPES : [],
    },
  })
})

export default router
