// src/app/(payload)/api/cron/send-reports/route.ts
// Scheduled Report Sending Cron Endpoint

import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import { ReportGeneratorService, ReportSchedule } from '@/lib/services/ReportGeneratorService'
import { requireCronAuth, withCronExecution } from '@/lib/security'
import {
  asObject,
  optionalNumber,
  validateJsonBody,
  validationIssue,
  validationErrorResponse,
  type ApiValidationResult,
} from '@/lib/validation'

// Status für Monitoring
let isRunning = false
let lastRunAt: Date | null = null
let lastResult: { success: boolean; sent: number; failed: number } | null = null

interface SendReportsBody {
  scheduleId: number
}

function validateSendReportsBody(input: unknown): ApiValidationResult<SendReportsBody> {
  const objectResult = asObject(input)
  if (!objectResult.valid) {
    return objectResult as ApiValidationResult<SendReportsBody>
  }

  const scheduleIdResult = optionalNumber(objectResult.data, 'scheduleId')
  if (!scheduleIdResult.valid) {
    return scheduleIdResult as ApiValidationResult<SendReportsBody>
  }

  if (scheduleIdResult.data === undefined) {
    return {
      valid: false,
      issues: [validationIssue('scheduleId', 'required', 'scheduleId is required')],
    }
  }

  return {
    valid: true,
    data: {
      scheduleId: scheduleIdResult.data,
    },
  }
}

/**
 * GET /api/cron/send-reports
 * Prüft und sendet fällige Reports
 *
 * Läuft stündlich und prüft welche Reports gesendet werden müssen
 */
export async function GET(request: NextRequest) {
  return withCronExecution(request, { endpoint: 'send-reports' }, async () => {
    if (isRunning) {
      return NextResponse.json(
        { error: 'Report sending already in progress' },
        { status: 423 },
      )
    }

    isRunning = true
    const startedAt = new Date()
    let sent = 0
    let failed = 0

    try {
      const payload = await getPayload({ config })
      const reportService = new ReportGeneratorService(payload)

      // Fällige Reports finden
      const now = new Date()
      const dueSchedules = await findDueSchedules(payload, now)

      console.log(`[Cron] Found ${dueSchedules.length} due report schedules`)

      for (const schedule of dueSchedules) {
        try {
          console.log(`[Cron] Generating report: ${schedule.name}`)

          // Report generieren
          const report = await reportService.generateReport(schedule)

          if (!report.success) {
            console.error(`[Cron] Report generation failed: ${report.error}`)
            failed++
            await updateScheduleError(payload, schedule.id, report.error || 'Generation failed')
            continue
          }

          // Report senden
          const sendSuccess = await reportService.sendReport(schedule, report)

          if (sendSuccess) {
            sent++
            console.log(`[Cron] Report sent successfully: ${schedule.name}`)

            // Nächsten Versandzeitpunkt berechnen
            await updateNextScheduledTime(payload, schedule)
          } else {
            failed++
            console.error(`[Cron] Report send failed: ${schedule.name}`)
          }
        } catch (error) {
          const message = error instanceof Error ? error.message : String(error)
          console.error(`[Cron] Error processing schedule ${schedule.name}:`, error)
          await updateScheduleError(payload, schedule.id, message)
          failed++
        }
      }

      lastRunAt = startedAt
      lastResult = { success: failed === 0, sent, failed }

      return NextResponse.json({
        success: failed === 0,
        message: `Reports processed: ${sent} sent, ${failed} failed`,
        processed: dueSchedules.length,
        sent,
        failed,
        duration: Date.now() - startedAt.getTime(),
      })
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error)
      console.error('[Cron] send-reports error:', error)

      lastResult = { success: false, sent, failed: failed + 1 }

      return NextResponse.json(
        { error: message, sent, failed },
        { status: 500 },
      )
    } finally {
      isRunning = false
    }
  })
}

/**
 * POST /api/cron/send-reports
 * Manuelles Senden eines bestimmten Reports
 */
export async function POST(request: NextRequest) {
  return withCronExecution(request, { endpoint: 'send-reports' }, async () => {
    try {
      const bodyResult = await validateJsonBody(request, validateSendReportsBody)
      if (!bodyResult.valid) {
        return validationErrorResponse(bodyResult.issues)
      }
      const { scheduleId } = bodyResult.data

      const payload = await getPayload({ config })

      // Schedule laden
      const scheduleDoc = await payload.findByID({
        collection: 'report-schedules',
        id: scheduleId,
        depth: 2,
      })

      if (!scheduleDoc) {
        return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })
      }

      const schedule = transformSchedule(scheduleDoc)
      const reportService = new ReportGeneratorService(payload)

      // Report generieren und senden
      const report = await reportService.generateReport(schedule)

      if (!report.success) {
        return NextResponse.json(
          { success: false, error: report.error },
          { status: 500 },
        )
      }

      const sendSuccess = await reportService.sendReport(schedule, report)

      return NextResponse.json({
        success: sendSuccess,
        scheduleName: schedule.name,
        format: schedule.format,
        recipients: schedule.recipients.length,
      })
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error)
      console.error('[Cron] POST send-reports error:', error)
      return NextResponse.json({ error: message }, { status: 500 })
    }
  })
}

/**
 * HEAD /api/cron/send-reports
 * Status-Check für Monitoring
 */
export async function HEAD(request: NextRequest) {
  const authError = requireCronAuth(request, 'send-reports')
  if (authError) {
    return new NextResponse(null, { status: authError.status })
  }

  return new NextResponse(null, {
    status: isRunning ? 423 : 200,
    headers: {
      'X-Running': isRunning.toString(),
      'X-Last-Run': lastRunAt?.toISOString() || 'never',
      'X-Last-Sent': lastResult?.sent.toString() || '0',
      'X-Last-Failed': lastResult?.failed.toString() || '0',
    },
  })
}

/**
 * Findet fällige Report-Schedules
 */
async function findDueSchedules(payload: any, now: Date): Promise<ReportSchedule[]> {
  // Schedules laden die:
  // 1. Aktiv sind
  // 2. nextScheduledAt <= jetzt
  const schedules = await payload.find({
    collection: 'report-schedules',
    where: {
      and: [
        { enabled: { equals: true } },
        {
          or: [
            { nextScheduledAt: { less_than_equal: now.toISOString() } },
            { nextScheduledAt: { equals: null } },
          ],
        },
      ],
    },
    depth: 2,
    limit: 50,
  })

  return schedules.docs.map(transformSchedule)
}

/**
 * Transformiert DB-Dokument zu ReportSchedule
 */
function transformSchedule(doc: any): ReportSchedule {
  return {
    id: doc.id,
    name: doc.name,
    enabled: doc.enabled,
    frequency: doc.frequency,
    dayOfWeek: doc.dayOfWeek,
    dayOfMonth: doc.dayOfMonth,
    time: doc.time,
    timezone: doc.timezone,
    reportType: doc.reportType,
    channels: doc.channels?.map((c: any) => ({
      id: typeof c === 'number' ? c : c.id,
      displayName: c.displayName,
    })),
    periodDays: doc.periodDays || 7,
    format: doc.format,
    recipients: doc.recipients || [],
  }
}

/**
 * Aktualisiert den nächsten Versandzeitpunkt
 */
async function updateNextScheduledTime(payload: any, schedule: ReportSchedule): Promise<void> {
  const next = calculateNextScheduledTime(schedule)

  await payload.update({
    collection: 'report-schedules',
    id: schedule.id,
    data: {
      nextScheduledAt: next?.toISOString(),
    },
  })
}

/**
 * Berechnet den nächsten Versandzeitpunkt
 */
function calculateNextScheduledTime(schedule: ReportSchedule): Date | null {
  const [hours, minutes] = schedule.time.split(':').map(Number)
  const now = new Date()
  const next = new Date()
  next.setHours(hours, minutes, 0, 0)

  // Mindestens 1 Stunde in der Zukunft
  if (next <= now) {
    next.setDate(next.getDate() + 1)
  }

  switch (schedule.frequency) {
    case 'daily':
      // Bereits korrekt
      break

    case 'weekly':
      const dayMap: Record<string, number> = {
        sunday: 0,
        monday: 1,
        tuesday: 2,
        wednesday: 3,
        thursday: 4,
        friday: 5,
        saturday: 6,
      }
      const targetDay = dayMap[schedule.dayOfWeek || 'monday']
      const currentDay = next.getDay()
      let daysUntil = targetDay - currentDay
      if (daysUntil <= 0) daysUntil += 7
      next.setDate(next.getDate() + daysUntil)
      break

    case 'monthly':
      const targetDate = schedule.dayOfMonth || 1
      next.setDate(targetDate)
      if (next <= now) {
        next.setMonth(next.getMonth() + 1)
      }
      break
  }

  return next
}

/**
 * Aktualisiert den Fehler-Status eines Schedules
 */
async function updateScheduleError(payload: any, id: number, error: string): Promise<void> {
  await payload.update({
    collection: 'report-schedules',
    id,
    data: {
      lastError: error.substring(0, 255),
    },
  })
}

export const dynamic = 'force-dynamic'
export const maxDuration = 120 // 2 Minuten max
