/**
 * Shutdown management for Node.js applications
 *
 * Features:
 * - Automatic signal handling (SIGTERM, SIGINT, etc.)
 * - Configurable shutdown timeout
 * - Multiple cleanup callbacks with error handling
 * - Platform-specific signal support (Windows/Unix)
 */

import type {
  PrioritizedShutdownCallback,
  ShutdownCallback,
  ShutdownOptions,
  ShutdownResult,
} from './types';
import { getLogger } from '@stock-bot/logger';

// Global flag that works across all processes/workers
declare global {
  var __SHUTDOWN_SIGNAL_RECEIVED__: boolean | undefined;
}

export class Shutdown {
  private static instance: Shutdown | null = null;
  private readonly logger = getLogger('shutdown');
  private isShuttingDown = false;
  private signalReceived = false; // Track if shutdown signal was received
  private shutdownTimeout = 30000; // 30 seconds default
  private callbacks: PrioritizedShutdownCallback[] = [];
  private signalHandlersRegistered = false;

  constructor(options: ShutdownOptions = {}) {
    this.shutdownTimeout = options.timeout || 30000;

    if (options.autoRegister !== false) {
      this.setupSignalHandlers();
    }
  }

  /**
   * Get or create singleton instance
   */
  static getInstance(options?: ShutdownOptions): Shutdown {
    if (!Shutdown.instance) {
      Shutdown.instance = new Shutdown(options);
    }
    return Shutdown.instance;
  }

  /**
   * Reset singleton instance (mainly for testing)
   */
  static reset(): void {
    Shutdown.instance = null;
  }

  /**
   * Register a cleanup callback with priority (lower numbers = higher priority)
   */
  onShutdown(callback: ShutdownCallback, priority: number = 50, name?: string): void {
    if (this.isShuttingDown) {
      return;
    }
    this.callbacks.push({ callback, priority, name });
  }

  /**
   * Register a high priority shutdown callback (for queues, critical services)
   */
  onShutdownHigh(callback: ShutdownCallback, name?: string): void {
    this.onShutdown(callback, 10, name);
  }

  /**
   * Register a medium priority shutdown callback (for databases, connections)
   */
  onShutdownMedium(callback: ShutdownCallback, name?: string): void {
    this.onShutdown(callback, 50, name);
  }

  /**
   * Register a low priority shutdown callback (for loggers, cleanup)
   */
  onShutdownLow(callback: ShutdownCallback, name?: string): void {
    this.onShutdown(callback, 90, name);
  }

  /**
   * Set shutdown timeout in milliseconds
   */
  setTimeout(timeout: number): void {
    if (timeout <= 0) {
      throw new Error('Shutdown timeout must be positive');
    }
    this.shutdownTimeout = timeout;
  }

  /**
   * Get current shutdown state
   */
  isShutdownInProgress(): boolean {
    return this.isShuttingDown;
  }

  /**
   * Check if shutdown signal was received (for quick checks in running jobs)
   */
  isShutdownSignalReceived(): boolean {
    return this.signalReceived || this.isShuttingDown;
  }

  /**
   * Get number of registered callbacks
   */
  getCallbackCount(): number {
    return this.callbacks.length;
  }

  /**
   * Initiate graceful shutdown
   */
  async shutdown(_signal?: string): Promise<ShutdownResult> {
    if (this.isShuttingDown) {
      return {
        success: false,
        callbacksExecuted: 0,
        callbacksFailed: 0,
        duration: 0,
        error: 'Shutdown already in progress',
      };
    }

    this.isShuttingDown = true;
    const startTime = Date.now();

    const shutdownPromise = this.executeCallbacks();
    const timeoutPromise = new Promise<never>((_, reject) => {
      setTimeout(() => reject(new Error('Shutdown timeout')), this.shutdownTimeout);
    });

    let result: ShutdownResult;

    try {
      const callbackResult = await Promise.race([shutdownPromise, timeoutPromise]);
      const duration = Date.now() - startTime;

      result = {
        success: true,
        callbacksExecuted: callbackResult.executed,
        callbacksFailed: callbackResult.failed,
        duration,
        error: callbackResult.failed > 0 ? `${callbackResult.failed} callbacks failed` : undefined,
      };
    } catch (error) {
      const duration = Date.now() - startTime;
      const errorMessage = error instanceof Error ? error.message : String(error);

      result = {
        success: false,
        callbacksExecuted: 0,
        callbacksFailed: 0,
        duration,
        error: errorMessage,
      };
    }

    // Don't call process.exit here - let the caller decide

    return result;
  }

  /**
   * Initiate shutdown and exit process
   */
  async shutdownAndExit(signal?: string, exitCode = 0): Promise<never> {
    const result = await this.shutdown(signal);
    const finalExitCode = result.success ? exitCode : 1;

    process.exit(finalExitCode);
  }

  /**
   * Execute all registered callbacks in priority order
   */
  private async executeCallbacks(): Promise<{ executed: number; failed: number }> {
    if (this.callbacks.length === 0) {
      return { executed: 0, failed: 0 };
    }

    // Sort callbacks by priority (lower numbers = higher priority = execute first)
    const sortedCallbacks = [...this.callbacks].sort((a, b) => a.priority - b.priority);

    let executed = 0;
    let failed = 0;

    // Execute callbacks in order by priority
    for (const { callback, name, priority } of sortedCallbacks) {
      try {
        await callback();
        executed++;
      } catch (error) {
        failed++;
        if (name) {
          this.logger.error(`Shutdown failed: ${name} (priority: ${priority})`, error);
        }
      }
    }

    return { executed, failed };
  }

  /**
   * Setup signal handlers for graceful shutdown
   */
  private setupSignalHandlers(): void {
    if (this.signalHandlersRegistered) {
      return;
    }

    // Platform-specific signals
    const signals: NodeJS.Signals[] =
      process.platform === 'win32' ? ['SIGINT', 'SIGTERM'] : ['SIGTERM', 'SIGINT', 'SIGUSR2'];

    signals.forEach(signal => {
      process.on(signal, () => {
        // Only process if not already shutting down
        if (!this.isShuttingDown) {
          // Set signal flag immediately for quick checks
          this.signalReceived = true;
          // Also set global flag for workers/other processes
          globalThis.__SHUTDOWN_SIGNAL_RECEIVED__ = true;
          this.shutdownAndExit(signal).catch(() => {
            process.exit(1);
          });
        }
      });
    });

    // Handle uncaught exceptions
    process.on('uncaughtException', () => {
      this.signalReceived = true;
      this.shutdownAndExit('uncaughtException', 1).catch(() => {
        process.exit(1);
      });
    });

    // Handle unhandled promise rejections
    process.on('unhandledRejection', () => {
      this.signalReceived = true;
      this.shutdownAndExit('unhandledRejection', 1).catch(() => {
        process.exit(1);
      });
    });

    this.signalHandlersRegistered = true;
  }
}
