import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject, forwardRef } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModuleRef } from '@nestjs/core';
import { PrismaService } from '../../../prisma/prisma.service';
import { CredentialsService } from '../credentials/credentials.service';
import { IntegrationType, SyncStatus } from '@prisma/client';

/**
 * Job data structure for sync jobs
 */
export interface SyncJobData {
  integrationId: string;
  integrationType: IntegrationType;
  fullSync: boolean;
}

/**
 * Job result structure
 */
export interface SyncJobResult {
  success: boolean;
  recordsProcessed: number;
  duration: number;
  error?: string;
}

/**
 * Integration Sync Processor
 *
 * This processor handles background sync jobs for integrations.
 * In a production environment with BullMQ configured, this would be a proper
 * BullMQ processor. For development without Redis, it uses scheduled intervals.
 *
 * Features:
 * - Scheduled sync jobs per integration type
 * - Retry logic (3 attempts)
 * - Concurrency control
 * - Job status tracking
 */
// Import SyncService type for lazy loading
import type { SyncService } from '../sync/sync.service';

@Injectable()
export class IntegrationSyncProcessor implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(IntegrationSyncProcessor.name);
  private readonly scheduledJobs = new Map<string, NodeJS.Timeout>();
  private readonly maxRetries = 3;
  private readonly concurrencyLimit = 3;
  private runningJobCount = 0;
  private isEnabled = false;
  private syncService: SyncService | null = null;

  // Default sync intervals in minutes (can be overridden by environment)
  private readonly defaultIntervals: Record<string, number> = {
    PLENTYONE: 15,
    ZULIP: 5,
    TODOIST: 10,
    FREESCOUT: 10,
    NEXTCLOUD: 30,
    ECODMS: 60,
    GEMBADOCS: 30,
    // Legacy types
    DATEV: 60,
    PERSONIO: 30,
    SAP: 60,
    CALENDAR: 15,
    EMAIL: 5,
    CUSTOM: 60,
  };

  constructor(
    private readonly configService: ConfigService,
    private readonly prisma: PrismaService,
    private readonly credentialsService: CredentialsService,
    private readonly moduleRef: ModuleRef,
  ) {}

  async onModuleInit(): Promise<void> {
    // Check if sync jobs should be enabled
    const enableSync = this.configService.get<string>('ENABLE_SYNC_JOBS', 'false');
    this.isEnabled = enableSync === 'true';

    if (!this.isEnabled) {
      this.logger.log(
        'Sync jobs are disabled. Set ENABLE_SYNC_JOBS=true to enable.',
      );
      return;
    }

    // Lazy load SyncService to avoid circular dependency
    try {
      const { SyncService } = await import('../sync/sync.service');
      this.syncService = this.moduleRef.get(SyncService, { strict: false });
    } catch (error) {
      this.logger.warn('Could not load SyncService. Sync jobs will use direct processing.');
    }

    this.logger.log('Initializing integration sync processor...');
    await this.initializeScheduledJobs();
  }

  onModuleDestroy(): void {
    this.logger.log('Shutting down integration sync processor...');
    this.stopAllJobs();
  }

  /**
   * Initializes scheduled sync jobs for all active credentials
   */
  async initializeScheduledJobs(): Promise<void> {
    const credentials = await this.prisma.integrationCredential.findMany({
      where: { isActive: true },
    });

    this.logger.log(`Found ${credentials.length} active credentials to schedule`);

    for (const credential of credentials) {
      this.scheduleJob(credential.id, credential.type);
    }
  }

  /**
   * Schedules a recurring sync job for a credential
   */
  scheduleJob(credentialId: string, type: IntegrationType): void {
    // Stop existing job if any
    this.stopJob(credentialId);

    const intervalMinutes = this.getIntervalForType(type);
    const intervalMs = intervalMinutes * 60 * 1000;

    this.logger.log(
      `Scheduling sync job for ${type}:${credentialId} every ${intervalMinutes} minutes`,
    );

    // Schedule the job
    const timeout = setInterval(async () => {
      await this.processJob({
        integrationId: credentialId,
        integrationType: type,
        fullSync: false,
      });
    }, intervalMs);

    this.scheduledJobs.set(credentialId, timeout);

    // Also run immediately on startup (with delay to allow app to fully start)
    setTimeout(() => {
      this.processJob({
        integrationId: credentialId,
        integrationType: type,
        fullSync: false,
      });
    }, 5000 + Math.random() * 10000); // Stagger initial syncs
  }

  /**
   * Stops a scheduled job
   */
  stopJob(credentialId: string): void {
    const timeout = this.scheduledJobs.get(credentialId);
    if (timeout) {
      clearInterval(timeout);
      this.scheduledJobs.delete(credentialId);
      this.logger.debug(`Stopped sync job for credential ${credentialId}`);
    }
  }

  /**
   * Stops all scheduled jobs
   */
  stopAllJobs(): void {
    for (const [credentialId, timeout] of this.scheduledJobs) {
      clearInterval(timeout);
      this.logger.debug(`Stopped sync job for credential ${credentialId}`);
    }
    this.scheduledJobs.clear();
  }

  /**
   * Processes a sync job with retry logic
   */
  async processJob(
    data: SyncJobData,
    attempt: number = 1,
  ): Promise<SyncJobResult> {
    const { integrationId, integrationType, fullSync } = data;

    // Check concurrency limit
    if (this.runningJobCount >= this.concurrencyLimit) {
      this.logger.warn(
        `Concurrency limit reached. Skipping sync for ${integrationId}`,
      );
      return {
        success: false,
        recordsProcessed: 0,
        duration: 0,
        error: 'Concurrency limit reached',
      };
    }

    this.runningJobCount++;
    const startTime = Date.now();

    try {
      this.logger.debug(
        `Processing sync job for ${integrationType}:${integrationId} (attempt ${attempt}/${this.maxRetries})`,
      );

      // Check if credential is still active
      const credential = await this.prisma.integrationCredential.findUnique({
        where: { id: integrationId },
      });

      if (!credential || !credential.isActive) {
        this.logger.debug(
          `Credential ${integrationId} is inactive or deleted. Stopping scheduled job.`,
        );
        this.stopJob(integrationId);
        return {
          success: false,
          recordsProcessed: 0,
          duration: Date.now() - startTime,
          error: 'Credential inactive or deleted',
        };
      }

      // Trigger the sync through SyncService (if available)
      if (!this.syncService) {
        throw new Error('SyncService not available');
      }
      const result = await this.syncService.triggerSync(integrationId, fullSync);

      const duration = Date.now() - startTime;

      this.logger.log(
        `Sync completed for ${integrationType}:${integrationId} in ${duration}ms`,
      );

      return {
        success: true,
        recordsProcessed: 0, // Actual count is in the sync history
        duration,
      };
    } catch (error) {
      const duration = Date.now() - startTime;
      const errorMessage =
        error instanceof Error ? error.message : 'Unknown error';

      this.logger.error(
        `Sync failed for ${integrationType}:${integrationId}: ${errorMessage}`,
      );

      // Retry if attempts remaining
      if (attempt < this.maxRetries) {
        const retryDelay = Math.pow(2, attempt) * 1000; // Exponential backoff
        this.logger.debug(
          `Retrying sync for ${integrationId} in ${retryDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`,
        );

        await this.delay(retryDelay);
        return this.processJob(data, attempt + 1);
      }

      return {
        success: false,
        recordsProcessed: 0,
        duration,
        error: errorMessage,
      };
    } finally {
      this.runningJobCount--;
    }
  }

  /**
   * Gets the sync interval for an integration type from config or default
   */
  private getIntervalForType(type: IntegrationType): number {
    const envKey = `SYNC_INTERVAL_${type}`;
    const envValue = this.configService.get<string>(envKey);

    if (envValue) {
      const parsed = parseInt(envValue, 10);
      if (!isNaN(parsed) && parsed > 0) {
        return parsed;
      }
    }

    return this.defaultIntervals[type] || 60;
  }

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

  /**
   * Manually triggers re-initialization of scheduled jobs
   * Useful when credentials are added/updated/deleted
   */
  async refreshScheduledJobs(): Promise<void> {
    if (!this.isEnabled) {
      return;
    }

    this.logger.log('Refreshing scheduled sync jobs...');
    this.stopAllJobs();
    await this.initializeScheduledJobs();
  }

  /**
   * Adds a new credential to the scheduler
   */
  addCredential(credentialId: string, type: IntegrationType): void {
    if (!this.isEnabled) {
      return;
    }

    this.scheduleJob(credentialId, type);
  }

  /**
   * Removes a credential from the scheduler
   */
  removeCredential(credentialId: string): void {
    this.stopJob(credentialId);
  }

  /**
   * Gets the current status of scheduled jobs
   */
  getSchedulerStatus(): {
    isEnabled: boolean;
    scheduledJobsCount: number;
    runningJobsCount: number;
    concurrencyLimit: number;
  } {
    return {
      isEnabled: this.isEnabled,
      scheduledJobsCount: this.scheduledJobs.size,
      runningJobsCount: this.runningJobCount,
      concurrencyLimit: this.concurrencyLimit,
    };
  }
}
