import cron from 'node-cron';
import { config } from './config/config.js';
import dbManager, { DatabaseManager } from './db/databaseManager.js';
import projectMetadata from './cache/projectMetadata.js';
import { createKafkaConsumers } from './kafka/consumer.js';
import { processKafkaMessage } from './processor/index.js';
import { extractStatusUpdate } from './processor/statusExtractor.js';
import { createRedisClient } from './redis/redisClient.js';
import { RedisIntegration } from './redis/redisIntegration.js';
import { buildErrorQueueKey, enqueueError, startErrorRetryWorker } from './redis/errorQueue.js';
import { MetricCollector } from './utils/metricCollector.js';
import { RoomStatusManager } from './db/roomStatusManager.js';
import { StatusBatchProcessor } from './db/statusBatchProcessor.js';
import { logger } from './utils/logger.js';
import { BatchProcessor } from './db/batchProcessor.js';

const bootstrap = async () => {
  // 0. Initialize Project Metadata Cache
  await projectMetadata.init();

  // Metric Collector
  const metricCollector = new MetricCollector();

  const redisClient = await createRedisClient(config.redis);
  const redisIntegration = new RedisIntegration(
    redisClient,
    config.redis.projectName,
    config.redis.apiBaseUrl
  );
  redisIntegration.startHeartbeat();

  // 1.2 Initialize Room Status Manager (independent pool for snapshot table)
  const roomStatusManagerG4 = new RoomStatusManager(config.roomStatusDb);
  const statusBatchProcessorG4 = new StatusBatchProcessor(roomStatusManagerG4, {
    flushInterval: 500,
    maxBufferSize: 200,
    targetName: 'g4:room_status.room_status_moment',
    redisIntegration
  });

  let roomStatusManagerG5 = null;
  let statusBatchProcessorG5 = null;
  if (config.roomStatusDbG5.enabled) {
    roomStatusManagerG5 = new RoomStatusManager(config.roomStatusDbG5, { omitGuid: true });
    statusBatchProcessorG5 = new StatusBatchProcessor(roomStatusManagerG5, {
      flushInterval: 500,
      maxBufferSize: 200,
      targetName: `g5:${config.roomStatusDbG5.schema}.${config.roomStatusDbG5.table}`,
      redisIntegration
    });
  }
  logger.info('Room Status sync pipeline initialized');

  // 1. Setup Metric Reporting Cron Job (Every minute)
  cron.schedule('* * * * *', async () => {
    const metrics = metricCollector.getAndReset();
    const report = `[Minute Metrics] Pulled: ${metrics.kafka_pulled}, Parse Error: ${metrics.parse_error}, Inserted: ${metrics.db_inserted}, Failed: ${metrics.db_failed}`;
    console.log(report);
    logger.info(report, metrics);

    try {
      await redisIntegration.info('Minute Metrics', metrics);
    } catch (err) {
      logger.error('Failed to report metrics to Redis', { error: err?.message });
    }
  });

  const errorQueueKey = buildErrorQueueKey(config.redis.projectName);

  const batchProcessorG4 = new BatchProcessor(dbManager, config, {
    batchSize: config.kafka.maxInFlight,
    targetName: 'g4:rcu_action.rcu_action_events',
    dbConfig: config.db
  });

  let dbManagerG5 = null;
  let batchProcessorG5 = null;
  if (config.dbG5.enabled) {
    dbManagerG5 = new DatabaseManager(config.dbG5);
    batchProcessorG5 = new BatchProcessor(dbManagerG5, config, {
      batchSize: config.kafka.maxInFlight,
      omitGuid: true,
      targetName: 'g5:rcu_action.rcu_action_events_g5',
      dbConfig: config.dbG5
    });
  }

  const handleMessage = async (message) => {
    if (message.topic) {
      metricCollector.increment('kafka_pulled');
    }
    try {
      const messageValue = Buffer.isBuffer(message.value)
        ? message.value.toString('utf8')
        : message.value;
      const messageKey = Buffer.isBuffer(message.key)
        ? message.key.toString('utf8')
        : message.key;
      const { rows, payload } = await processKafkaMessage({ message });

      // Status sync must be independent from action-event DB write failures.
      try {
        const statusUpdate = extractStatusUpdate(payload);
        if (statusUpdate) {
          if (config.roomStatusDb.enabled && config.writeToG4Status) {
            statusBatchProcessorG4.add(statusUpdate);
          }
          if (statusBatchProcessorG5 && config.roomStatusDbG5.enabled && config.writeToG5Status) {
            statusBatchProcessorG5.add(statusUpdate);
          }
        }
      } catch (statusErr) {
        logger.error('Status extraction failed (non-blocking)', { error: statusErr?.message });
      }

      let inserted = 0;
      const dbActions = [];
      if (config.db.enabled && config.writeToG4Action) {
        dbActions.push(batchProcessorG4.add({ rows }).then(c => { inserted = Math.max(inserted, c); }));
      }
      if (batchProcessorG5 && config.dbG5.enabled && config.writeToG5Action) {
        dbActions.push(batchProcessorG5.add({ rows }).then(c => { inserted = Math.max(inserted, c); }));
      }

      const dbResults = await Promise.allSettled(dbActions);
      const failedDbResults = dbResults.filter(r => r.status === 'rejected');

      if (failedDbResults.length > 0) {
        metricCollector.increment('db_failed');
        failedDbResults.forEach((result) => {
          const err = result.reason;
          logger.warn('Action event insert failed and skipped (non-blocking)', {
            error: err?.message,
            type: err?.type,
            dbContext: err?.dbContext
          });
        });
      }

      if (dbResults.some(r => r.status === 'fulfilled')) {
        metricCollector.increment('db_inserted');
      }

      logger.info('Kafka message processed', {
        inserted,
        statusTargets: {
          g4: config.roomStatusDb.enabled,
          g5: Boolean(statusBatchProcessorG5 && config.roomStatusDbG5.enabled)
        }
      });
    } catch (error) {
      if (error.type === 'PARSE_ERROR') {
        metricCollector.increment('parse_error');
      } else {
        metricCollector.increment('db_failed');
      }
      logger.error('Message processing failed', {
        error: error?.message,
        type: error?.type,
        stack: error?.stack,
        rawPayload: error?.rawPayload,
        validationIssues: error?.validationIssues,
        dbContext: error?.dbContext
      });
      throw error; // Re-throw to trigger onError
    }
  };

  const handleError = async (error, message) => {
    logger.error('Kafka processing error', {
      error: error?.message,
      type: error?.type,
      stack: error?.stack
    });
    try {
      await redisIntegration.error('Kafka processing error', {
        module: 'kafka',
        stack: error?.stack || error?.message
      });
    } catch (redisError) {
      logger.error('Redis error log failed', { error: redisError?.message });
    }
    if (message) {
      const messageValue = Buffer.isBuffer(message.value)
        ? message.value.toString('utf8')
        : message.value;
      try {
        await enqueueError(redisClient, errorQueueKey, {
          attempts: 0,
          value: messageValue,
          meta: {
            topic: message.topic,
            partition: message.partition,
            offset: message.offset,
            key: message.key
          },
          timestamp: Date.now()
        });
      } catch (enqueueError) {
        logger.error('Enqueue error payload failed', { error: enqueueError?.message });
      }
    }
  };

  const healthCheck = {
    shouldPause: async (error) => {
      if (error?.type === 'DB_ERROR') {
        const checks = [];
        if (config.db.enabled) checks.push(dbManager.testConnection());
        if (dbManagerG5 && config.dbG5.enabled) checks.push(dbManagerG5.testConnection());
        const results = await Promise.all(checks);
        return results.some(res => !res);
      }
      return false;
    },
    check: async () => {
      const checks = [];
      if (config.db.enabled) checks.push(dbManager.testConnection());
      if (dbManagerG5 && config.dbG5.enabled) checks.push(dbManagerG5.testConnection());
      const results = await Promise.all(checks);
      return results.every(res => res === true);
    }
  };

  const consumers = createKafkaConsumers({
    kafkaConfig: config.kafka,
    onMessage: handleMessage,
    onError: handleError,
    healthCheck
  });

  // Start retry worker (non-blocking)
  startErrorRetryWorker({
    client: redisClient,
    queueKey: errorQueueKey,
    redisIntegration,
    handler: async (item) => {
      if (!item?.value) {
        throw new Error('Missing value in retry payload');
      }
      await handleMessage({ value: item.value });
    }
  }).catch(err => {
    logger.error('Retry worker failed', { error: err?.message });
  });

  // Graceful Shutdown Logic
  const shutdown = async (signal) => {
    logger.info(`Received ${signal}, shutting down...`);

    try {
      // 1. Close Kafka Consumer
      if (consumers && consumers.length > 0) {
        await Promise.all(consumers.map(c => new Promise((resolve) => c.close(true, resolve))));
        logger.info('Kafka consumer closed', { count: consumers.length });
      }

      // 2. Stop Redis Heartbeat (if method exists, otherwise just close client)
      // redisIntegration.stopHeartbeat(); // Assuming implementation or just rely on client close

      // 3. Close Redis Client
      await redisClient.quit();
      logger.info('Redis client closed');

      // 4. Flush and close Room Status pipeline
      try {
        if (config.roomStatusDb.enabled) {
          await statusBatchProcessorG4.flush();
          await roomStatusManagerG4.close();
        }
        if (statusBatchProcessorG5 && config.roomStatusDbG5.enabled) {
          await statusBatchProcessorG5.flush();
          await roomStatusManagerG5.close();
        }
        logger.info('Room Status pipelines closed');
      } catch (rsErr) {
        logger.error('Error closing Room Status pipeline', { error: rsErr?.message });
      }

      // 5. Close Database Pools
      if (config.db.enabled) {
        await dbManager.close();
      }
      if (dbManagerG5 && config.dbG5.enabled) {
        await dbManagerG5.close();
      }
      logger.info('Database connections closed');

      process.exit(0);
    } catch (err) {
      logger.error('Error during shutdown', { error: err?.message });
      process.exit(1);
    }
  };

  process.on('SIGTERM', () => shutdown('SIGTERM'));
  process.on('SIGINT', () => shutdown('SIGINT'));
};

bootstrap().catch((error) => {
  logger.error('Service bootstrap failed', { error: error?.message });
  process.exit(1);
});
