// src/lib/jobs/TokenRefreshService.ts
// Automatischer Token Refresh Service für alle Plattformen

import { getPayload, Payload } from 'payload'
import config from '@payload-config'
import { refreshAccessToken as refreshYouTubeToken } from '../integrations/youtube/oauth'
import {
  refreshLongLivedToken as refreshMetaToken,
  isTokenValid as isMetaTokenValid,
} from '../integrations/meta/oauth'
import { JobLogger } from './JobLogger'
import { NotificationService } from './NotificationService'

// =============================================================================
// Types
// =============================================================================

export type TokenPlatform = 'youtube' | 'facebook' | 'instagram'

export interface TokenRefreshResult {
  accountId: number
  accountName: string
  platform: TokenPlatform
  success: boolean
  previousExpiry: Date | null
  newExpiry: Date | null
  error?: string
  action: 'refreshed' | 'skipped' | 'failed' | 'expired'
}

export interface TokenRefreshSummary {
  success: boolean
  startedAt: Date
  completedAt: Date
  duration: number
  results: TokenRefreshResult[]
  stats: {
    total: number
    refreshed: number
    skipped: number
    failed: number
    expired: number
  }
  notifications: TokenNotification[]
}

export interface TokenNotification {
  type: 'warning' | 'error' | 'info'
  accountId: number
  accountName: string
  platform: TokenPlatform
  message: string
  expiresAt?: Date
  daysUntilExpiry?: number
}

export interface TokenRefreshOptions {
  /** Nur bestimmte Plattformen prüfen */
  platforms?: TokenPlatform[]
  /** Nur bestimmte Account-IDs prüfen */
  accountIds?: number[]
  /** Tage vor Ablauf für Refresh (default: 7) */
  refreshThresholdDays?: number
  /** Auch bereits abgelaufene Tokens versuchen zu erneuern */
  includeExpired?: boolean
  /** Dry-Run: Nur prüfen, nicht erneuern */
  dryRun?: boolean
}

// =============================================================================
// Global State
// =============================================================================

let isRunning = false
let lastRunAt: Date | null = null
let lastResult: TokenRefreshSummary | null = null

// =============================================================================
// Token Refresh Service
// =============================================================================

export class TokenRefreshService {
  private payload: Payload
  private logger: JobLogger
  private notificationService: NotificationService

  constructor(payload: Payload) {
    this.payload = payload
    this.logger = new JobLogger('token-refresh')
    this.notificationService = new NotificationService(payload)
  }

  /**
   * Prüft und erneuert alle Tokens die bald ablaufen
   */
  async refreshExpiringTokens(options: TokenRefreshOptions = {}): Promise<TokenRefreshSummary> {
    const {
      platforms,
      accountIds,
      refreshThresholdDays = 7,
      includeExpired = false,
      dryRun = false,
    } = options

    const startedAt = new Date()
    const results: TokenRefreshResult[] = []
    const notifications: TokenNotification[] = []

    if (isRunning) {
      return {
        success: false,
        startedAt,
        completedAt: new Date(),
        duration: 0,
        results: [],
        stats: { total: 0, refreshed: 0, skipped: 0, failed: 0, expired: 0 },
        notifications: [
          {
            type: 'warning',
            accountId: 0,
            accountName: 'System',
            platform: 'youtube',
            message: 'Token refresh already running',
          },
        ],
      }
    }

    isRunning = true

    try {
      this.logger.info('Starting token refresh check', {
        refreshThresholdDays,
        dryRun,
        platforms,
      })

      // Alle aktiven Accounts laden
      const accounts = await this.loadAccounts(platforms, accountIds)
      this.logger.info(`Found ${accounts.length} accounts to check`)

      const thresholdDate = new Date()
      thresholdDate.setDate(thresholdDate.getDate() + refreshThresholdDays)

      for (const account of accounts) {
        const result = await this.processAccount(account, {
          thresholdDate,
          includeExpired,
          dryRun,
        })

        results.push(result)

        // Benachrichtigungen generieren
        if (result.action === 'expired') {
          notifications.push({
            type: 'error',
            accountId: result.accountId,
            accountName: result.accountName,
            platform: result.platform,
            message: `Token abgelaufen! Bitte neu authentifizieren.`,
            expiresAt: result.previousExpiry || undefined,
          })
        } else if (result.action === 'failed') {
          notifications.push({
            type: 'error',
            accountId: result.accountId,
            accountName: result.accountName,
            platform: result.platform,
            message: `Token-Refresh fehlgeschlagen: ${result.error}`,
          })
        } else if (result.action === 'refreshed') {
          const daysUntilExpiry = result.newExpiry
            ? Math.ceil((result.newExpiry.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
            : undefined

          notifications.push({
            type: 'info',
            accountId: result.accountId,
            accountName: result.accountName,
            platform: result.platform,
            message: `Token erfolgreich erneuert`,
            expiresAt: result.newExpiry || undefined,
            daysUntilExpiry,
          })

          // Warnung wenn neue Gültigkeit unter 14 Tagen liegt
          if (daysUntilExpiry !== undefined && daysUntilExpiry < 14) {
            notifications.push({
              type: 'warning',
              accountId: result.accountId,
              accountName: result.accountName,
              platform: result.platform,
              message: `Token nur noch ${daysUntilExpiry} Tage gültig nach Refresh`,
              expiresAt: result.newExpiry || undefined,
              daysUntilExpiry,
            })
          }
        }

        // Rate Limiting
        await this.sleep(200)
      }

      // Statistiken berechnen
      const stats = {
        total: results.length,
        refreshed: results.filter((r) => r.action === 'refreshed').length,
        skipped: results.filter((r) => r.action === 'skipped').length,
        failed: results.filter((r) => r.action === 'failed').length,
        expired: results.filter((r) => r.action === 'expired').length,
      }

      const summary: TokenRefreshSummary = {
        success: stats.failed === 0 && stats.expired === 0,
        startedAt,
        completedAt: new Date(),
        duration: Date.now() - startedAt.getTime(),
        results,
        stats,
        notifications,
      }

      lastRunAt = startedAt
      lastResult = summary

      // Benachrichtigungen in Datenbank persistieren
      await this.persistNotifications(notifications)

      this.logger.summary({
        success: summary.success,
        processed: stats.total,
        errors: stats.failed + stats.expired,
      })

      return summary
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error)
      this.logger.error('Token refresh failed', error)

      return {
        success: false,
        startedAt,
        completedAt: new Date(),
        duration: Date.now() - startedAt.getTime(),
        results,
        stats: {
          total: results.length,
          refreshed: results.filter((r) => r.action === 'refreshed').length,
          skipped: results.filter((r) => r.action === 'skipped').length,
          failed: results.filter((r) => r.action === 'failed').length + 1,
          expired: results.filter((r) => r.action === 'expired').length,
        },
        notifications: [
          {
            type: 'error',
            accountId: 0,
            accountName: 'System',
            platform: 'youtube',
            message: `System error: ${errorMessage}`,
          },
        ],
      }
    } finally {
      isRunning = false
    }
  }

  /**
   * Lädt alle relevanten Accounts
   */
  private async loadAccounts(
    platforms?: TokenPlatform[],
    accountIds?: number[]
  ): Promise<any[]> {
    const where: any = {
      and: [{ isActive: { equals: true } }],
    }

    if (accountIds && accountIds.length > 0) {
      where.and.push({ id: { in: accountIds } })
    }

    const accounts = await this.payload.find({
      collection: 'social-accounts',
      where,
      limit: 200,
      depth: 2,
    })

    // Nach Plattform filtern
    return accounts.docs.filter((account) => {
      const platform = this.detectPlatform(account)
      if (!platform) return false
      if (platforms && platforms.length > 0) {
        return platforms.includes(platform)
      }
      return true
    })
  }

  /**
   * Erkennt die Plattform eines Accounts
   */
  private detectPlatform(account: any): TokenPlatform | null {
    const platformDoc = account.platform as { slug?: string; apiConfig?: { apiType?: string } }
    const slug = platformDoc?.slug?.toLowerCase()
    const apiType = platformDoc?.apiConfig?.apiType

    if (slug === 'youtube' || apiType === 'youtube_v3') return 'youtube'
    if (slug === 'facebook' || apiType === 'facebook_graph' || apiType === 'meta_graph')
      return 'facebook'
    if (slug === 'instagram' || apiType === 'instagram_graph') return 'instagram'

    return null
  }

  /**
   * Verarbeitet einen einzelnen Account
   */
  private async processAccount(
    account: any,
    options: {
      thresholdDate: Date
      includeExpired: boolean
      dryRun: boolean
    }
  ): Promise<TokenRefreshResult> {
    const { thresholdDate, includeExpired, dryRun } = options
    const accountId = account.id as number
    const accountName = (account.displayName as string) || `Account ${accountId}`
    const platform = this.detectPlatform(account)!

    const credentials = account.credentials as {
      accessToken?: string
      refreshToken?: string
      tokenExpiresAt?: string
    } | undefined

    // Kein Token vorhanden
    if (!credentials?.accessToken) {
      return {
        accountId,
        accountName,
        platform,
        success: false,
        previousExpiry: null,
        newExpiry: null,
        action: 'skipped',
        error: 'No access token',
      }
    }

    // Ablaufdatum prüfen
    const expiresAt = credentials.tokenExpiresAt
      ? new Date(credentials.tokenExpiresAt)
      : null

    // Token ohne Ablaufdatum (YouTube mit Refresh Token) - nur prüfen wenn explizit angefragt
    if (!expiresAt && platform === 'youtube' && credentials.refreshToken) {
      return {
        accountId,
        accountName,
        platform,
        success: true,
        previousExpiry: null,
        newExpiry: null,
        action: 'skipped',
      }
    }

    // Token noch lange gültig
    if (expiresAt && expiresAt > thresholdDate) {
      const daysLeft = Math.ceil((expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
      this.logger.info(`${accountName}: Token valid for ${daysLeft} more days`)

      return {
        accountId,
        accountName,
        platform,
        success: true,
        previousExpiry: expiresAt,
        newExpiry: expiresAt,
        action: 'skipped',
      }
    }

    // Token abgelaufen
    const isExpired = expiresAt && expiresAt < new Date()
    if (isExpired && !includeExpired) {
      this.logger.warn(`${accountName}: Token expired, re-authentication required`)

      return {
        accountId,
        accountName,
        platform,
        success: false,
        previousExpiry: expiresAt,
        newExpiry: null,
        action: 'expired',
        error: 'Token expired, re-authentication required',
      }
    }

    // Dry-Run: Nur melden
    if (dryRun) {
      this.logger.info(`${accountName}: Would refresh token (dry-run)`)

      return {
        accountId,
        accountName,
        platform,
        success: true,
        previousExpiry: expiresAt,
        newExpiry: null,
        action: 'skipped',
      }
    }

    // Token erneuern
    try {
      const newExpiry = await this.refreshToken(account, platform, credentials)

      this.logger.info(`${accountName}: Token refreshed, new expiry: ${newExpiry?.toISOString()}`)

      return {
        accountId,
        accountName,
        platform,
        success: true,
        previousExpiry: expiresAt,
        newExpiry,
        action: 'refreshed',
      }
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error)
      this.logger.error(`${accountName}: Token refresh failed`, error)

      return {
        accountId,
        accountName,
        platform,
        success: false,
        previousExpiry: expiresAt,
        newExpiry: null,
        action: 'failed',
        error: errorMessage,
      }
    }
  }

  /**
   * Erneuert den Token für einen Account
   */
  private async refreshToken(
    account: any,
    platform: TokenPlatform,
    credentials: {
      accessToken?: string
      refreshToken?: string
      tokenExpiresAt?: string
    }
  ): Promise<Date | null> {
    const accountId = account.id as number

    switch (platform) {
      case 'youtube':
        return this.refreshYouTubeToken(accountId, credentials)

      case 'facebook':
      case 'instagram':
        return this.refreshMetaToken(accountId, credentials)

      default:
        throw new Error(`Unsupported platform: ${platform}`)
    }
  }

  /**
   * Erneuert YouTube Token
   */
  private async refreshYouTubeToken(
    accountId: number,
    credentials: { refreshToken?: string }
  ): Promise<Date | null> {
    if (!credentials.refreshToken) {
      throw new Error('No refresh token available')
    }

    const newCredentials = await refreshYouTubeToken(credentials.refreshToken)

    const newExpiry = newCredentials.expiry_date
      ? new Date(newCredentials.expiry_date)
      : null

    await this.payload.update({
      collection: 'social-accounts',
      id: accountId,
      data: {
        credentials: {
          accessToken: newCredentials.access_token || undefined,
          refreshToken: newCredentials.refresh_token || credentials.refreshToken,
          tokenExpiresAt: newExpiry?.toISOString(),
        },
      },
    })

    return newExpiry
  }

  /**
   * Erneuert Meta (Facebook/Instagram) Token
   */
  private async refreshMetaToken(
    accountId: number,
    credentials: { accessToken?: string }
  ): Promise<Date | null> {
    if (!credentials.accessToken) {
      throw new Error('No access token available')
    }

    // Prüfen ob Token noch gültig ist (für Refresh muss er noch gültig sein!)
    const isValid = await isMetaTokenValid(credentials.accessToken)
    if (!isValid) {
      throw new Error('Token already expired, re-authentication required')
    }

    // Long-lived Token erneuern
    const newToken = await refreshMetaToken(credentials.accessToken)

    const newExpiry = new Date(Date.now() + newToken.expires_in * 1000)

    await this.payload.update({
      collection: 'social-accounts',
      id: accountId,
      data: {
        credentials: {
          accessToken: newToken.access_token,
          tokenExpiresAt: newExpiry.toISOString(),
        },
      },
    })

    return newExpiry
  }

  /**
   * Persistiert Benachrichtigungen in die Datenbank
   */
  private async persistNotifications(notifications: TokenNotification[]): Promise<void> {
    // Nur error und warning notifications persistieren
    const criticalNotifications = notifications.filter(
      (n) => n.type === 'error' || n.type === 'warning'
    )

    if (criticalNotifications.length === 0) {
      return
    }

    this.logger.info(`Persisting ${criticalNotifications.length} notifications`)

    for (const notification of criticalNotifications) {
      try {
        switch (notification.type) {
          case 'error':
            if (notification.message.includes('abgelaufen') || notification.message.includes('expired')) {
              await this.notificationService.notifyTokenExpired(
                notification.accountId,
                notification.accountName,
                notification.platform
              )
            } else {
              await this.notificationService.notifyTokenRefreshFailed(
                notification.accountId,
                notification.accountName,
                notification.platform,
                notification.message
              )
            }
            break

          case 'warning':
            if (notification.daysUntilExpiry !== undefined) {
              await this.notificationService.notifyTokenExpiring(
                notification.accountId,
                notification.accountName,
                notification.platform,
                notification.daysUntilExpiry
              )
            }
            break
        }
      } catch (error) {
        this.logger.error(`Failed to persist notification for ${notification.accountName}`, error)
      }
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms))
  }
}

// =============================================================================
// Convenience Functions
// =============================================================================

/**
 * Führt Token-Refresh aus (Singleton-Pattern)
 */
export async function runTokenRefresh(
  options: TokenRefreshOptions = {}
): Promise<TokenRefreshSummary> {
  const payload = await getPayload({ config })
  const service = new TokenRefreshService(payload)
  return service.refreshExpiringTokens(options)
}

/**
 * Gibt den Status des letzten Token-Refresh zurück
 */
export function getTokenRefreshStatus(): {
  isRunning: boolean
  lastRunAt: string | null
  lastResult: TokenRefreshSummary | null
} {
  return {
    isRunning,
    lastRunAt: lastRunAt?.toISOString() || null,
    lastResult,
  }
}

/**
 * Setzt den Status zurück (für Tests)
 */
export function resetTokenRefreshStatus(): void {
  isRunning = false
  lastRunAt = null
  lastResult = null
}

export default TokenRefreshService
