import { Repository } from 'typeorm';
import {
  ReportSchedule,
  ScheduleExecution,
  ReportExecution,
  ExecutionStatus,
} from '../entities';
import { logger } from '../../../shared/utils/logger.js';
import { ReportExecutionService } from './report-execution.service';

/**
 * ReportSchedulerService
 *
 * Handles scheduled report execution, delivery, and tracking.
 */
export class ReportSchedulerService {
  constructor(
    private readonly scheduleRepository: Repository<ReportSchedule>,
    private readonly scheduleExecutionRepository: Repository<ScheduleExecution>,
    private readonly reportExecutionService: ReportExecutionService
  ) {}

  /**
   * Process all due schedules
   */
  async processDueSchedules(): Promise<void> {
    const dueSchedules = await this.scheduleRepository
      .createQueryBuilder('schedule')
      .where('schedule.is_active = true')
      .andWhere('schedule.next_run_at <= NOW()')
      .leftJoinAndSelect('schedule.reportDefinition', 'definition')
      .leftJoinAndSelect('schedule.recipients', 'recipients', 'recipients.is_active = true')
      .getMany();

    logger.info('Processing due schedules', { count: dueSchedules.length });

    for (const schedule of dueSchedules) {
      try {
        await this.executeSchedule(schedule);
      } catch (error: any) {
        logger.error('Failed to execute schedule', {
          scheduleId: schedule.id,
          error: error.message,
        });
      }
    }
  }

  /**
   * Execute a specific schedule
   */
  async executeSchedule(schedule: ReportSchedule): Promise<ScheduleExecution> {
    logger.info('Executing scheduled report', {
      scheduleId: schedule.id,
      name: schedule.name,
    });

    let scheduleExecution: ScheduleExecution;
    let reportExecution: ReportExecution | null = null;

    try {
      // Execute the report
      reportExecution = await this.reportExecutionService.executeReport(
        {
          reportDefinitionId: schedule.reportDefinitionId,
          parameters: schedule.parameters,
          exportFormat: schedule.exportFormat,
        },
        schedule.tenantId,
        schedule.createdBy || 'system'
      );

      // Wait for completion (with timeout)
      const completedExecution = await this.waitForCompletion(reportExecution.id, schedule.tenantId);

      // Create schedule execution record
      scheduleExecution = this.scheduleExecutionRepository.create({
        scheduleId: schedule.id,
        executionId: completedExecution.id,
        status: completedExecution.status,
        executedAt: new Date(),
      });

      // Deliver if successful
      if (completedExecution.status === ExecutionStatus.COMPLETED) {
        const recipientsNotified = await this.deliverReport(schedule, completedExecution);
        scheduleExecution.recipientsNotified = recipientsNotified;
        scheduleExecution.deliveryStatus = { success: true, notified: recipientsNotified };
      }

    } catch (error: any) {
      scheduleExecution = this.scheduleExecutionRepository.create({
        scheduleId: schedule.id,
        executionId: reportExecution?.id || null,
        status: ExecutionStatus.FAILED,
        errorMessage: error.message,
        executedAt: new Date(),
      });
    }

    // Save schedule execution
    const saved = await this.scheduleExecutionRepository.save(scheduleExecution);

    // Update schedule with last run info
    await this.updateScheduleAfterRun(schedule, scheduleExecution);

    return saved;
  }

  /**
   * Wait for report execution to complete
   */
  private async waitForCompletion(
    executionId: string,
    tenantId: string,
    timeoutMs: number = 300000 // 5 minutes
  ): Promise<ReportExecution> {
    const startTime = Date.now();
    const pollInterval = 2000; // 2 seconds

    while (Date.now() - startTime < timeoutMs) {
      const execution = await this.reportExecutionService.findById(executionId, tenantId);

      if (
        execution.status === ExecutionStatus.COMPLETED ||
        execution.status === ExecutionStatus.FAILED ||
        execution.status === ExecutionStatus.CANCELLED
      ) {
        return execution;
      }

      // Wait before next poll
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }

    throw new Error('Report execution timed out');
  }

  /**
   * Deliver report to recipients
   */
  private async deliverReport(
    schedule: ReportSchedule,
    execution: ReportExecution
  ): Promise<number> {
    const activeRecipients = schedule.recipients?.filter(r => r.isActive) || [];

    if (activeRecipients.length === 0) {
      logger.warn('No active recipients for schedule', { scheduleId: schedule.id });
      return 0;
    }

    let notifiedCount = 0;

    switch (schedule.deliveryMethod) {
      case 'email':
        notifiedCount = await this.deliverByEmail(schedule, execution, activeRecipients);
        break;
      case 'storage':
        notifiedCount = await this.deliverToStorage(schedule, execution);
        break;
      case 'webhook':
        notifiedCount = await this.deliverByWebhook(schedule, execution);
        break;
      default:
        logger.debug('No delivery method configured', { scheduleId: schedule.id });
    }

    return notifiedCount;
  }

  /**
   * Deliver report by email
   */
  private async deliverByEmail(
    schedule: ReportSchedule,
    execution: ReportExecution,
    recipients: any[]
  ): Promise<number> {
    // TODO: Implement email delivery using notification service
    logger.info('Email delivery requested', {
      scheduleId: schedule.id,
      recipientCount: recipients.length,
    });

    // Placeholder - would integrate with email service
    return recipients.length;
  }

  /**
   * Deliver report to storage
   */
  private async deliverToStorage(
    schedule: ReportSchedule,
    execution: ReportExecution
  ): Promise<number> {
    // TODO: Implement storage delivery
    logger.info('Storage delivery requested', { scheduleId: schedule.id });
    return 1;
  }

  /**
   * Deliver report via webhook
   */
  private async deliverByWebhook(
    schedule: ReportSchedule,
    execution: ReportExecution
  ): Promise<number> {
    // TODO: Implement webhook delivery
    const webhookUrl = schedule.deliveryConfig?.webhookUrl;
    logger.info('Webhook delivery requested', { scheduleId: schedule.id, webhookUrl });
    return 1;
  }

  /**
   * Update schedule after execution
   */
  private async updateScheduleAfterRun(
    schedule: ReportSchedule,
    scheduleExecution: ScheduleExecution
  ): Promise<void> {
    const nextRunAt = this.calculateNextRun(schedule.cronExpression, schedule.timezone);

    await this.scheduleRepository.update(schedule.id, {
      lastRunAt: new Date(),
      lastRunStatus: scheduleExecution.status,
      nextRunAt,
      runCount: schedule.runCount + 1,
    });
  }

  /**
   * Calculate next run time from cron expression
   */
  private calculateNextRun(cronExpression: string, timezone: string): Date {
    // Simple implementation - would use a proper cron parser library
    // For now, default to next day at same time
    const nextRun = new Date();
    nextRun.setDate(nextRun.getDate() + 1);
    return nextRun;
  }

  /**
   * Get execution history for a schedule
   */
  async getScheduleHistory(
    scheduleId: string,
    limit: number = 20
  ): Promise<ScheduleExecution[]> {
    return this.scheduleExecutionRepository.find({
      where: { scheduleId },
      relations: ['execution'],
      order: { executedAt: 'DESC' },
      take: limit,
    });
  }

  /**
   * Manually trigger a schedule
   */
  async triggerSchedule(scheduleId: string, tenantId: string): Promise<ScheduleExecution> {
    const schedule = await this.scheduleRepository.findOne({
      where: { id: scheduleId, tenantId },
      relations: ['reportDefinition', 'recipients'],
    });

    if (!schedule) {
      throw new Error('Schedule not found');
    }

    return this.executeSchedule(schedule);
  }
}
