// Report Scheduler Service
// Handles scheduling, subscriptions, and delivery of reports

import { prisma } from '@/lib/db';
import { generateReport, generateHTMLReport, generateCSV } from './report-generator';
import type { ReportColumn } from './report-definitions';
import {
  sendReportEmail,
  sendScheduleConfirmationEmail,
  extractSummaryMetrics,
  formatPeriodLabel,
} from './email-delivery';

export interface ScheduleConfig {
  frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly' | 'custom';
  cronExpression?: string;
  timezone?: string;
  dayOfWeek?: number;
  dayOfMonth?: number;
  monthOfYear?: number;
  timeOfDay?: string;
}

export interface DeliveryConfig {
  method: 'email' | 'in_app' | 'both';
  format: 'pdf' | 'excel' | 'csv' | 'html';
  recipients: string[];
  ccRecipients?: string[];
  subject?: string;
  body?: string;
  includeLink?: boolean;
}

// Create or update a report schedule
export async function createReportSchedule(
  reportId: string,
  name: string,
  schedule: ScheduleConfig,
  delivery: DeliveryConfig,
  filters: Record<string, unknown> = {},
  createdById?: string
): Promise<{ id: string; nextRunAt: Date | null }> {
  const nextRunAt = calculateNextRun(schedule);
  
  const result = await prisma.reportSchedule.create({
    data: {
      reportId,
      name,
      frequency: schedule.frequency,
      cronExpression: schedule.cronExpression,
      timezone: schedule.timezone || 'UTC',
      dayOfWeek: schedule.dayOfWeek,
      dayOfMonth: schedule.dayOfMonth,
      timeOfDay: schedule.timeOfDay || '09:00',
      format: delivery.format,
      deliveryMethod: delivery.method,
      recipients: JSON.parse(JSON.stringify(delivery.recipients)),
      ccRecipients: JSON.parse(JSON.stringify(delivery.ccRecipients || [])),
      subject: delivery.subject,
      body: delivery.body,
      includeLink: delivery.includeLink ?? true,
      filters: JSON.parse(JSON.stringify(filters)),
      isEnabled: true,
      nextRunAt,
      createdById,
    },
  });
  
  return { id: result.id, nextRunAt: result.nextRunAt };
}

// Update a report schedule
export async function updateReportSchedule(
  scheduleId: string,
  updates: Partial<{
    name: string;
    schedule: ScheduleConfig;
    delivery: DeliveryConfig;
    filters: Record<string, unknown>;
    isEnabled: boolean;
  }>
): Promise<{ id: string; nextRunAt: Date | null }> {
  const updateData: Record<string, unknown> = {};
  
  if (updates.name) updateData.name = updates.name;
  if (updates.filters) updateData.filters = updates.filters;
  if (updates.isEnabled !== undefined) updateData.isEnabled = updates.isEnabled;
  
  if (updates.schedule) {
    updateData.frequency = updates.schedule.frequency;
    updateData.cronExpression = updates.schedule.cronExpression;
    updateData.timezone = updates.schedule.timezone;
    updateData.dayOfWeek = updates.schedule.dayOfWeek;
    updateData.dayOfMonth = updates.schedule.dayOfMonth;
    updateData.timeOfDay = updates.schedule.timeOfDay;
    updateData.nextRunAt = calculateNextRun(updates.schedule);
  }
  
  if (updates.delivery) {
    updateData.format = updates.delivery.format;
    updateData.deliveryMethod = updates.delivery.method;
    updateData.recipients = updates.delivery.recipients;
    updateData.ccRecipients = updates.delivery.ccRecipients;
    updateData.subject = updates.delivery.subject;
    updateData.body = updates.delivery.body;
    updateData.includeLink = updates.delivery.includeLink;
  }
  
  const result = await prisma.reportSchedule.update({
    where: { id: scheduleId },
    data: updateData,
  });
  
  return { id: result.id, nextRunAt: result.nextRunAt };
}

// Delete a report schedule
export async function deleteReportSchedule(scheduleId: string): Promise<void> {
  await prisma.reportSchedule.delete({ where: { id: scheduleId } });
}

// Subscribe user to a report
export async function subscribeToReport(
  userId: string,
  reportId: string,
  frequency: 'immediate' | 'daily' | 'weekly' | 'monthly',
  options: {
    dayOfWeek?: number;
    dayOfMonth?: number;
    timeOfDay?: string;
    timezone?: string;
    format?: 'pdf' | 'excel' | 'csv';
    deliveryChannel?: 'email' | 'in_app' | 'both';
    filters?: Record<string, unknown>;
  } = {}
): Promise<{ id: string; nextDeliveryAt: Date | null }> {
  const nextDeliveryAt = calculateNextDelivery(frequency, options);
  
  const result = await prisma.reportSubscription.upsert({
    where: {
      userId_reportId: { userId, reportId },
    },
    update: {
      frequency,
      dayOfWeek: options.dayOfWeek,
      dayOfMonth: options.dayOfMonth,
      timeOfDay: options.timeOfDay || '09:00',
      timezone: options.timezone,
      format: options.format || 'pdf',
      deliveryChannel: options.deliveryChannel || 'email',
      filters: JSON.parse(JSON.stringify(options.filters || {})),
      isActive: true,
      nextDeliveryAt,
    },
    create: {
      userId,
      reportId,
      frequency,
      dayOfWeek: options.dayOfWeek,
      dayOfMonth: options.dayOfMonth,
      timeOfDay: options.timeOfDay || '09:00',
      timezone: options.timezone,
      format: options.format || 'pdf',
      deliveryChannel: options.deliveryChannel || 'email',
      filters: JSON.parse(JSON.stringify(options.filters || {})),
      isActive: true,
      nextDeliveryAt,
    },
  });
  
  return { id: result.id, nextDeliveryAt: result.nextDeliveryAt };
}

// Unsubscribe from a report
export async function unsubscribeFromReport(userId: string, reportId: string): Promise<void> {
  await prisma.reportSubscription.update({
    where: {
      userId_reportId: { userId, reportId },
    },
    data: { isActive: false },
  });
}

// Get user's report subscriptions
export async function getUserSubscriptions(userId: string) {
  return prisma.reportSubscription.findMany({
    where: { userId, isActive: true },
    include: { report: true },
    orderBy: { createdAt: 'desc' },
  });
}

// Get all schedules for a report
export async function getReportSchedules(reportId: string) {
  return prisma.reportSchedule.findMany({
    where: { reportId },
    orderBy: { createdAt: 'desc' },
  });
}

// Execute a scheduled report
export async function executeScheduledReport(scheduleId: string): Promise<{ generatedReportId: string }> {
  const schedule = await prisma.reportSchedule.findUnique({
    where: { id: scheduleId },
    include: { report: true },
  });
  
  if (!schedule || !schedule.isEnabled) {
    throw new Error('Schedule not found or disabled');
  }
  
  // Generate the report
  const reportData = await generateReport({
    reportId: schedule.reportId,
    filters: schedule.filters as Record<string, unknown>,
    format: 'json',
  });
  
  // Create generated report record
  const generatedReport = await prisma.generatedReport.create({
    data: {
      reportId: schedule.reportId,
      scheduleId: schedule.id,
      name: `${schedule.report.name} - ${new Date().toISOString().split('T')[0]}`,
      triggerType: 'scheduled',
      status: 'completed',
      filters: JSON.parse(JSON.stringify(schedule.filters || {})),
      format: schedule.format,
      rowCount: reportData.metadata.rowCount,
      duration: reportData.metadata.executionTime,
      summaryData: JSON.parse(JSON.stringify({ totals: reportData.totals })),
      chartData: reportData.chartData ? JSON.parse(JSON.stringify(reportData.chartData)) : undefined,
      expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days
    },
  });
  
  // Generate output file
  let fileContent = '';
  const columns = (schedule.report.columns as unknown) as ReportColumn[];
  
  if (schedule.format === 'csv') {
    fileContent = generateCSV(columns, reportData);
  } else if (schedule.format === 'html') {
    fileContent = generateHTMLReport(schedule.report.name, columns, reportData);
  }
  
  // Send email notification if delivery method includes email
  if (schedule.deliveryMethod === 'email' || schedule.deliveryMethod === 'both') {
    const recipients = (schedule.recipients as string[]) || [];
    const ccRecipients = (schedule.ccRecipients as string[]) || [];
    
    if (recipients.length > 0) {
      // Extract metrics for email summary
      const summaryMetrics = extractSummaryMetrics(
        { totals: reportData.totals as Record<string, number> },
        columns
          .filter((c) => c.format === 'currency' || c.format === 'number')
          .slice(0, 4)
          .map((c) => ({
            key: c.key,
            label: c.label,
            format: c.format as 'currency' | 'number' | 'percentage',
            color: 'cyan' as const,
          }))
      );
      
      await sendReportEmail({
        recipients: recipients.map((email) => ({ email })),
        ccRecipients: ccRecipients.map((email) => ({ email })),
        reportName: schedule.report.name,
        reportDescription: schedule.report.description || undefined,
        scheduleName: schedule.name,
        periodLabel: formatPeriodLabel(undefined, undefined, schedule.frequency),
        summaryMetrics,
        tableData: {
          columns,
          rows: (reportData.rows || []).slice(0, 10),
        },
        detailLink: process.env.NEXTAUTH_URL ? `${process.env.NEXTAUTH_URL}/admin/reports` : undefined,
        format: schedule.report.module === 'executive' ? 'executive' : 'standard',
      });
    }
  }
  
  // Update schedule
  const scheduleConfig: ScheduleConfig = {
    frequency: schedule.frequency as ScheduleConfig['frequency'],
    cronExpression: schedule.cronExpression || undefined,
    timezone: schedule.timezone,
    dayOfWeek: schedule.dayOfWeek || undefined,
    dayOfMonth: schedule.dayOfMonth || undefined,
    timeOfDay: schedule.timeOfDay,
  };
  
  await prisma.reportSchedule.update({
    where: { id: scheduleId },
    data: {
      lastRunAt: new Date(),
      nextRunAt: calculateNextRun(scheduleConfig),
      runCount: { increment: 1 },
    },
  });
  
  return { generatedReportId: generatedReport.id };
}

// Execute a subscription delivery
export async function executeSubscriptionDelivery(subscriptionId: string): Promise<{ generatedReportId: string }> {
  const subscription = await prisma.reportSubscription.findUnique({
    where: { id: subscriptionId },
    include: { report: true, user: true },
  });
  
  if (!subscription || !subscription.isActive) {
    throw new Error('Subscription not found or inactive');
  }
  
  // Generate the report
  const reportData = await generateReport({
    reportId: subscription.reportId,
    filters: subscription.filters as Record<string, unknown>,
    format: 'json',
  });
  
  // Create generated report record
  const generatedReport = await prisma.generatedReport.create({
    data: {
      reportId: subscription.reportId,
      name: `${subscription.report.name} - ${new Date().toISOString().split('T')[0]}`,
      generatedFor: subscription.userId,
      triggerType: 'subscription',
      status: 'completed',
      filters: JSON.parse(JSON.stringify(subscription.filters || {})),
      format: subscription.format,
      rowCount: reportData.metadata.rowCount,
      duration: reportData.metadata.executionTime,
      summaryData: JSON.parse(JSON.stringify({ totals: reportData.totals })),
      chartData: reportData.chartData ? JSON.parse(JSON.stringify(reportData.chartData)) : undefined,
      expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days
    },
  });
  
  // Send email to user if delivery channel includes email
  if (subscription.deliveryChannel === 'email' || subscription.deliveryChannel === 'both') {
    const columns = (subscription.report.columns as unknown) as ReportColumn[];
    
    // Extract metrics for email summary
    const summaryMetrics = extractSummaryMetrics(
      { totals: reportData.totals as Record<string, number> },
      columns
        .filter((c) => c.format === 'currency' || c.format === 'number')
        .slice(0, 4)
        .map((c) => ({
          key: c.key,
          label: c.label,
          format: c.format as 'currency' | 'number' | 'percentage',
          color: 'cyan' as const,
        }))
    );
    
    await sendReportEmail({
      recipients: [{ email: subscription.user.email, name: subscription.user.name || undefined }],
      reportName: subscription.report.name,
      reportDescription: subscription.report.description || undefined,
      periodLabel: formatPeriodLabel(undefined, undefined, subscription.frequency),
      summaryMetrics,
      tableData: {
        columns,
        rows: (reportData.rows || []).slice(0, 10),
      },
      detailLink: process.env.NEXTAUTH_URL ? `${process.env.NEXTAUTH_URL}/admin/reports` : undefined,
      format: subscription.report.module === 'executive' ? 'executive' : 'standard',
    });
  }
  
  // Update subscription
  await prisma.reportSubscription.update({
    where: { id: subscriptionId },
    data: {
      lastDeliveredAt: new Date(),
      nextDeliveryAt: calculateNextDelivery(subscription.frequency as 'daily' | 'weekly' | 'monthly', {
        dayOfWeek: subscription.dayOfWeek || undefined,
        dayOfMonth: subscription.dayOfMonth || undefined,
        timeOfDay: subscription.timeOfDay,
      }),
      deliveryCount: { increment: 1 },
    },
  });
  
  return { generatedReportId: generatedReport.id };
}

// Get due schedules
export async function getDueSchedules(): Promise<{ id: string; reportId: string; name: string }[]> {
  return prisma.reportSchedule.findMany({
    where: {
      isEnabled: true,
      nextRunAt: { lte: new Date() },
    },
    select: { id: true, reportId: true, name: true },
  });
}

// Get due subscriptions
export async function getDueSubscriptions(): Promise<{ id: string; userId: string; reportId: string }[]> {
  return prisma.reportSubscription.findMany({
    where: {
      isActive: true,
      nextDeliveryAt: { lte: new Date() },
    },
    select: { id: true, userId: true, reportId: true },
  });
}

// Calculate next run time for a schedule
function calculateNextRun(schedule: ScheduleConfig): Date {
  const now = new Date();
  const [hours, minutes] = (schedule.timeOfDay || '09:00').split(':').map(Number);
  
  let next = new Date(now);
  next.setHours(hours, minutes, 0, 0);
  
  // If time has passed today, start from tomorrow
  if (next <= now) {
    next.setDate(next.getDate() + 1);
  }
  
  switch (schedule.frequency) {
    case 'daily':
      // Already set to next occurrence
      break;
      
    case 'weekly':
      const targetDay = schedule.dayOfWeek ?? 1; // Default Monday
      while (next.getDay() !== targetDay) {
        next.setDate(next.getDate() + 1);
      }
      break;
      
    case 'monthly':
      const targetDate = schedule.dayOfMonth ?? 1;
      next.setDate(targetDate);
      if (next <= now) {
        next.setMonth(next.getMonth() + 1);
      }
      break;
      
    case 'quarterly':
      const currentMonth = next.getMonth();
      const nextQuarterMonth = Math.ceil((currentMonth + 1) / 3) * 3;
      next.setMonth(nextQuarterMonth);
      next.setDate(1);
      break;
      
    case 'yearly':
      const targetMonth = schedule.monthOfYear ?? 1;
      next.setMonth(targetMonth - 1);
      next.setDate(schedule.dayOfMonth ?? 1);
      if (next <= now) {
        next.setFullYear(next.getFullYear() + 1);
      }
      break;
  }
  
  return next;
}

// Calculate next delivery time for a subscription
function calculateNextDelivery(
  frequency: 'immediate' | 'daily' | 'weekly' | 'monthly',
  options: { dayOfWeek?: number; dayOfMonth?: number; timeOfDay?: string }
): Date | null {
  if (frequency === 'immediate') return null;
  
  return calculateNextRun({
    frequency: frequency as ScheduleConfig['frequency'],
    dayOfWeek: options.dayOfWeek,
    dayOfMonth: options.dayOfMonth,
    timeOfDay: options.timeOfDay,
  });
}

// Get generated report history
export async function getGeneratedReports(
  options: {
    reportId?: string;
    userId?: string;
    scheduleId?: string;
    triggerType?: string;
    limit?: number;
    offset?: number;
  } = {}
) {
  const where: Record<string, unknown> = {};
  
  if (options.reportId) where.reportId = options.reportId;
  if (options.userId) where.generatedFor = options.userId;
  if (options.scheduleId) where.scheduleId = options.scheduleId;
  if (options.triggerType) where.triggerType = options.triggerType;
  
  return prisma.generatedReport.findMany({
    where,
    include: { report: { select: { name: true, module: true } } },
    orderBy: { createdAt: 'desc' },
    take: options.limit || 50,
    skip: options.offset || 0,
  });
}
