/**
 * ServiceApplication - Common service initialization and lifecycle management
 * Encapsulates common patterns for Hono-based microservices
 */

import type { BaseAppConfig, UnifiedAppConfig } from '@stock-bot/config';
import { toUnifiedConfig } from '@stock-bot/config';
import type { HandlerRegistry } from '@stock-bot/handler-registry';
import { getLogger, setLoggerConfig, shutdownLoggers, type Logger } from '@stock-bot/logger';
import { Shutdown, SHUTDOWN_DEFAULTS } from '@stock-bot/shutdown';
import type { IServiceContainer } from '@stock-bot/types';
import type { AwilixContainer } from 'awilix';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import type { ServiceDefinitions } from './container/types';

/**
 * Configuration for ServiceApplication
 */
export interface ServiceApplicationConfig {
  /** Service name for logging and identification */
  serviceName: string;

  /** CORS configuration - if not provided, uses permissive defaults */
  corsConfig?: Parameters<typeof cors>[0];

  /** Whether to enable handler initialization */
  enableHandlers?: boolean;

  /** Whether to enable scheduled job creation */
  enableScheduledJobs?: boolean;

  /** Custom shutdown timeout in milliseconds */
  shutdownTimeout?: number;

  /** Service metadata for info endpoint */
  serviceMetadata?: {
    version?: string;
    description?: string;
    endpoints?: Record<string, string>;
  };

  /** Whether to add a basic info endpoint at root */
  addInfoEndpoint?: boolean;
}

/**
 * Lifecycle hooks for service customization
 */
export interface ServiceLifecycleHooks {
  /** Called after container is created but before routes */
  onContainerReady?: (container: IServiceContainer) => Promise<void> | void;

  /** Called after app is created but before routes are mounted */
  onAppReady?: (app: Hono, container: IServiceContainer) => Promise<void> | void;

  /** Called after routes are mounted but before server starts */
  onBeforeStart?: (app: Hono, container: IServiceContainer) => Promise<void> | void;

  /** Called after successful server startup */
  onStarted?: (port: number) => Promise<void> | void;

  /** Called during shutdown before cleanup */
  onBeforeShutdown?: () => Promise<void> | void;
}

/**
 * ServiceApplication - Manages the complete lifecycle of a microservice
 */
export class ServiceApplication {
  private config: UnifiedAppConfig;
  private serviceConfig: ServiceApplicationConfig;
  private hooks: ServiceLifecycleHooks;
  private logger: Logger;

  private container: AwilixContainer<ServiceDefinitions> | null = null;
  private serviceContainer: IServiceContainer | null = null;
  private app: Hono | null = null;
  private server: ReturnType<typeof Bun.serve> | null = null;
  private shutdown: Shutdown;

  constructor(
    config: BaseAppConfig | UnifiedAppConfig,
    serviceConfig: ServiceApplicationConfig,
    hooks: ServiceLifecycleHooks = {}
  ) {
    // Convert to unified config
    this.config = toUnifiedConfig(config);

    // Ensure service name is set in config
    if (!this.config.service.serviceName) {
      this.config.service.serviceName = serviceConfig.serviceName;
    }

    this.serviceConfig = {
      shutdownTimeout: 15000,
      enableHandlers: false,
      enableScheduledJobs: false,
      addInfoEndpoint: true,
      ...serviceConfig,
    };
    this.hooks = hooks;

    // Initialize logger configuration
    this.configureLogger();
    this.logger = getLogger(this.serviceConfig.serviceName);

    // Initialize shutdown manager
    this.shutdown = Shutdown.getInstance({
      timeout: this.serviceConfig.shutdownTimeout,
    });
  }

  /**
   * Configure logger based on application config
   */
  private configureLogger(): void {
    if (this.config.log) {
      setLoggerConfig({
        logLevel: this.config.log.level,
        logConsole: true,
        logFile: false,
        environment: this.config.environment,
        hideObject: this.config.log.hideObject,
      });
    }
  }

  /**
   * Create and configure Hono application with CORS
   */
  private createApp(): Hono {
    const app = new Hono();

    // Add CORS middleware with service-specific or default configuration
    const corsConfig = this.serviceConfig.corsConfig || {
      origin: '*',
      allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
      allowHeaders: ['Content-Type', 'Authorization'],
      credentials: false,
    };

    app.use('*', cors(corsConfig));

    // Add basic info endpoint if enabled
    if (this.serviceConfig.addInfoEndpoint) {
      const metadata = this.serviceConfig.serviceMetadata || {};
      app.get('/', c => {
        return c.json({
          name: this.serviceConfig.serviceName,
          version: metadata.version || '1.0.0',
          description: metadata.description,
          status: 'running',
          timestamp: new Date().toISOString(),
          endpoints: metadata.endpoints || {},
        });
      });
    }

    return app;
  }

  /**
   * Register graceful shutdown handlers
   */
  private registerShutdownHandlers(): void {
    this.logger.info('Registering shutdown handlers', {
      service: this.serviceConfig.serviceName,
      scheduledJobs: this.serviceConfig.enableScheduledJobs,
      timeout: this.serviceConfig.shutdownTimeout
    });

    // Priority 1: Queue system (highest priority)
    if (this.serviceConfig.enableScheduledJobs) {
      this.shutdown.onShutdown(
        async () => {
          this.logger.info('Shutting down queue system...');
          const startTime = Date.now();
          try {
            const queueManager = this.container?.resolve('queueManager');
            if (queueManager) {
              await queueManager.shutdown();
              const duration = Date.now() - startTime;
              this.logger.info('Queue system shut down successfully', { duration });
            } else {
              this.logger.warn('No queue manager found to shut down');
            }
          } catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('Error shutting down queue system', { error, duration });
          }
        },
        SHUTDOWN_DEFAULTS.HIGH_PRIORITY,
        'Queue System'
      );
    }

    // Priority 1: HTTP Server (high priority)
    this.shutdown.onShutdown(
      async () => {
        if (this.server) {
          this.logger.info('Stopping HTTP server...');
          const startTime = Date.now();
          try {
            this.server.stop();
            const duration = Date.now() - startTime;
            this.logger.info('HTTP server stopped successfully', { duration });
          } catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('Error stopping HTTP server', { error, duration });
          }
        } else {
          this.logger.warn('No HTTP server to stop');
        }
      },
      SHUTDOWN_DEFAULTS.HIGH_PRIORITY,
      'HTTP Server'
    );

    // Custom shutdown hook
    if (this.hooks.onBeforeShutdown) {
      this.shutdown.onShutdown(
        async () => {
          try {
            await this.hooks.onBeforeShutdown!();
          } catch (error) {
            this.logger.error('Error in custom shutdown hook', { error });
          }
        },
        SHUTDOWN_DEFAULTS.HIGH_PRIORITY,
        'Custom Shutdown'
      );
    }

    // Priority 2: Services and connections (medium priority)
    this.shutdown.onShutdown(
      async () => {
        this.logger.info('Disposing services and connections...');
        const startTime = Date.now();
        const disposed: string[] = [];
        const failed: string[] = [];
        
        try {
          if (this.container) {
            // Disconnect database clients
            const mongoClient = this.container.resolve('mongoClient');
            if (mongoClient?.disconnect) {
              try {
                const mongoStart = Date.now();
                await mongoClient.disconnect();
                disposed.push(`MongoDB (${Date.now() - mongoStart}ms)`);
              } catch (error) {
                this.logger.error('Failed to disconnect MongoDB', { error });
                failed.push('MongoDB');
              }
            }

            const postgresClient = this.container.resolve('postgresClient');
            if (postgresClient?.disconnect) {
              try {
                const pgStart = Date.now();
                await postgresClient.disconnect();
                disposed.push(`PostgreSQL (${Date.now() - pgStart}ms)`);
              } catch (error) {
                this.logger.error('Failed to disconnect PostgreSQL', { error });
                failed.push('PostgreSQL');
              }
            }

            const questdbClient = this.container.resolve('questdbClient');
            if (questdbClient?.disconnect) {
              try {
                const questStart = Date.now();
                await questdbClient.disconnect();
                disposed.push(`QuestDB (${Date.now() - questStart}ms)`);
              } catch (error) {
                this.logger.error('Failed to disconnect QuestDB', { error });
                failed.push('QuestDB');
              }
            }

            const duration = Date.now() - startTime;
            this.logger.info('Service disposal completed', { 
              duration, 
              disposed, 
              failed,
              success: failed.length === 0 
            });
          } else {
            this.logger.warn('No container available for service disposal');
          }
        } catch (error) {
          const duration = Date.now() - startTime;
          this.logger.error('Error disposing services', { error, duration, disposed, failed });
        }
      },
      SHUTDOWN_DEFAULTS.MEDIUM_PRIORITY,
      'Services'
    );

    // Priority 3: Logger shutdown (lowest priority - runs last)
    this.shutdown.onShutdown(
      async () => {
        try {
          this.logger.info('Shutting down loggers...');
          // const startTime = Date.now();
          await shutdownLoggers();
          // Log one final message before shutdown
          // console.log(`[${new Date().toISOString()}] Loggers shut down successfully (${Date.now() - startTime}ms)`);
        } catch (error) {
          // Use console.error since logger might be partially shut down
          console.error(`[${new Date().toISOString()}] Error shutting down loggers:`, error);
        }
      },
      SHUTDOWN_DEFAULTS.LOW_PRIORITY,
      'Loggers'
    );
    
    this.logger.info('All shutdown handlers registered', {
      service: this.serviceConfig.serviceName,
      handlerCount: 3 + (this.serviceConfig.enableScheduledJobs ? 1 : 0) + (this.hooks.onBeforeShutdown ? 1 : 0)
    });
  }

  /**
   * Start the service with full initialization
   */
  async start(
    containerFactory: (config: UnifiedAppConfig) => Promise<AwilixContainer<ServiceDefinitions>>,
    routeFactory: (container: IServiceContainer) => Hono,
    handlerInitializer?: (container: IServiceContainer) => Promise<void>
  ): Promise<void> {
    this.logger.info(`Initializing ${this.serviceConfig.serviceName} service...`);

    try {
      // Create and initialize container
      this.logger.debug('Creating DI container...');
      // Config already has service name from constructor
      this.container = await containerFactory(this.config);
      this.serviceContainer = this.container!.resolve('serviceContainer');
      this.logger.info('DI container created and initialized');

      // Call container ready hook
      if (this.hooks.onContainerReady) {
        await this.hooks.onContainerReady(this.serviceContainer);
      }

      // Create Hono application
      this.app = this.createApp();

      // Call app ready hook
      if (this.hooks.onAppReady) {
        await this.hooks.onAppReady(this.app, this.serviceContainer);
      }

      // Initialize handlers if enabled
      if (this.serviceConfig.enableHandlers && handlerInitializer) {
        this.logger.debug('Initializing handlers...');
        // Pass the service container with the DI container attached
        const containerWithDI = Object.assign({}, this.serviceContainer, {
          _diContainer: this.container,
        });
        await handlerInitializer(containerWithDI);
        this.logger.info('Handlers initialized');
        
        // After handlers are initialized, add rate limit rules to queue manager
        const queueManager = this.container.resolve('queueManager');
        const handlerRegistry = this.container.resolve<HandlerRegistry>('handlerRegistry');
        
        if (queueManager && handlerRegistry) {
          const rateLimitRules = [];
          const allMetadata = handlerRegistry.getAllMetadata();
          
          for (const [handlerName, metadata] of allMetadata) {
            // Handler-level rate limit
            if (metadata.rateLimit) {
              rateLimitRules.push({
                level: 'handler' as const,
                handler: handlerName,
                config: metadata.rateLimit as any, // Type mismatch between packages
              });
            }
            
            // Operation-level rate limits
            for (const operation of metadata.operations) {
              if (operation.rateLimit) {
                rateLimitRules.push({
                  level: 'operation' as const,
                  handler: handlerName,
                  operation: operation.name,
                  config: operation.rateLimit as any, // Type mismatch between packages
                });
              }
            }
          }
          
          if (rateLimitRules.length > 0) {
            await queueManager.addRateLimitRules(rateLimitRules);
            this.logger.info('Rate limit rules added to QueueManager', {
              ruleCount: rateLimitRules.length,
            });
          }
        }
      }

      // Create and mount routes
      const routes = routeFactory(this.serviceContainer);
      this.app.route('/', routes);

      // Initialize scheduled jobs if enabled
      if (this.serviceConfig.enableScheduledJobs) {
        await this.initializeScheduledJobs();
      }

      // Call before start hook
      if (this.hooks.onBeforeStart) {
        await this.hooks.onBeforeStart(this.app, this.serviceContainer);
      }

      // Register shutdown handlers
      this.registerShutdownHandlers();

      // Start HTTP server
      const port = this.config.service.port;
      this.server = Bun.serve({
        port,
        fetch: this.app.fetch,
        development: this.config.environment === 'development',
      });

      this.logger.info(`${this.serviceConfig.serviceName} service started on port ${port}`);

      // Call started hook
      if (this.hooks.onStarted) {
        await this.hooks.onStarted(port);
      }
    } catch (error) {
      this.logger.error('DETAILED ERROR:', error);
      this.logger.error('Failed to start service', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        details: JSON.stringify(error, null, 2),
      });
      throw error;
    }
  }

  /**
   * Initialize scheduled jobs from handler registry
   */
  private async initializeScheduledJobs(): Promise<void> {
    if (!this.container) {
      throw new Error('Container not initialized');
    }

    this.logger.debug('Creating scheduled jobs from registered handlers...');
    const handlerRegistry = this.container.resolve<HandlerRegistry>('handlerRegistry');
    const allHandlers = handlerRegistry.getAllHandlersWithSchedule();

    this.logger.info(
      `Found ${allHandlers.size} handlers with scheduled jobs: ${Array.from(allHandlers.keys()).join(', ')}`
    );

    let totalScheduledJobs = 0;
    for (const [handlerName, config] of allHandlers) {
      if (config.scheduledJobs && config.scheduledJobs.length > 0) {
        // Check if this handler belongs to the current service
        const ownerService = handlerRegistry.getHandlerService(handlerName);

        if (ownerService !== this.config.service.serviceName) {
          this.logger.trace('Skipping scheduled jobs for handler from different service', {
            handler: handlerName,
            ownerService,
            currentService: this.config.service.serviceName,
          });
          continue;
        }

        const queueManager = this.container.resolve('queueManager');
        if (!queueManager) {
          this.logger.error('Queue manager is not initialized, cannot create scheduled jobs');
          continue;
        }
        // Pass the handler registry explicitly when creating queues for scheduled jobs
        this.logger.debug('Creating queue for scheduled jobs', {
          handlerName,
          hasHandlerRegistry: !!handlerRegistry,
          registeredHandlers: handlerRegistry.getHandlerNames(),
        });

        const queue = queueManager.getQueue(handlerName, {
          handlerRegistry: handlerRegistry,
        });

        for (const scheduledJob of config.scheduledJobs) {
          // Include handler and operation info in job data
          const jobData = {
            handler: handlerName,
            operation: scheduledJob.operation,
            payload: scheduledJob.payload,
          };

          // Build job options from scheduled job config
          const jobOptions = {
            priority: scheduledJob.priority,
            delay: scheduledJob.delay,
            repeat: {
              immediately: scheduledJob.immediately,
            },
          };

          this.logger.debug('Adding scheduled job', {
            handler: handlerName,
            operation: scheduledJob.operation,
            hasOperation: !!handlerRegistry.getOperation(handlerName, scheduledJob.operation),
          });

          await queue.addScheduledJob(
            scheduledJob.operation,
            jobData,
            scheduledJob.cronPattern,
            jobOptions
          );
          totalScheduledJobs++;
          this.logger.debug('Scheduled job created', {
            handler: handlerName,
            operation: scheduledJob.operation,
            cronPattern: scheduledJob.cronPattern,
            immediately: scheduledJob.immediately,
            priority: scheduledJob.priority,
          });
        }
      }
    }
    this.logger.info('Scheduled jobs created', { totalJobs: totalScheduledJobs });

    // Start queue workers
    this.logger.debug('Starting queue workers...');
    const queueManager = this.container.resolve('queueManager');
    if (queueManager) {
      queueManager.startAllWorkers();
      this.logger.info('Queue workers started');
    }
  }

  /**
   * Stop the service gracefully
   */
  async stop(): Promise<void> {
    this.logger.info(`Stopping ${this.serviceConfig.serviceName} service...`);
    await this.shutdown.shutdown();
  }

  /**
   * Get the service container (for testing or advanced use cases)
   */
  getServiceContainer(): IServiceContainer | null {
    return this.serviceContainer;
  }

  /**
   * Get the Hono app (for testing or advanced use cases)
   */
  getApp(): Hono | null {
    return this.app;
  }
}
