/**
 * Monitoring Service
 * Collects health and performance metrics from all system components
 */

import * as os from 'os';
import type { IServiceContainer } from '@stock-bot/handlers';
import { getLogger } from '@stock-bot/logger';
import type {
  CacheStats,
  DatabaseStats,
  ProxyStats,
  QueueStats,
  ServiceMetrics,
  ServiceStatus,
  SystemHealth,
  SystemOverview,
} from '../types/monitoring.types';

export class MonitoringService {
  private readonly logger = getLogger('monitoring-service');
  private startTime = Date.now();

  constructor(private readonly container: IServiceContainer) {}

  /**
   * Get cache/Dragonfly statistics
   */
  async getCacheStats(): Promise<CacheStats> {
    try {
      if (!this.container.cache) {
        return {
          provider: 'dragonfly',
          connected: false,
        };
      }

      // Check if cache is connected using the isReady method
      const isConnected = this.container.cache.isReady();
      if (!isConnected) {
        return {
          provider: 'dragonfly',
          connected: false,
        };
      }

      // Get cache stats from the provider
      const cacheStats = this.container.cache.getStats();

      // Since we can't access Redis info directly, we'll use what's available
      return {
        provider: 'dragonfly',
        connected: true,
        uptime: cacheStats.uptime,
        stats: {
          hits: cacheStats.hits,
          misses: cacheStats.misses,
          keys: 0, // We can't get total keys without direct Redis access
          evictedKeys: 0,
          expiredKeys: 0,
        },
      };
    } catch (error) {
      this.logger.error('Failed to get cache stats', { error });
      return {
        provider: 'dragonfly',
        connected: false,
      };
    }
  }

  /**
   * Get queue statistics
   */
  async getQueueStats(): Promise<QueueStats[]> {
    const stats: QueueStats[] = [];

    try {
      if (!this.container.queueManager) {
        this.logger.warn('No queue manager available');
        return stats;
      }

      // Get all queue names from the QueueManager
      const queueManager = this.container.queueManager as any;
      this.logger.debug('Queue manager type:', {
        type: queueManager.constructor.name,
        hasGetAllQueues: typeof queueManager.getAllQueues === 'function',
        hasQueues: !!queueManager.queues,
        hasGetQueue: typeof queueManager.getQueue === 'function',
      });

      // Always use the known queue names since web-api doesn't create worker queues
      const handlerMapping = {
        qm: 'data-ingestion',
        ib: 'data-ingestion',
        ceo: 'data-ingestion',
        webshare: 'data-ingestion',
        exchanges: 'data-pipeline',
        symbols: 'data-pipeline',
      };

      // Worker configuration per queue (from service configs)
      const workerConfig: Record<string, { count: number; concurrency: number }> = {
        qm: { count: 1, concurrency: 2 },
        ib: { count: 1, concurrency: 1 },
        ceo: { count: 1, concurrency: 2 },
        webshare: { count: 1, concurrency: 1 },
        exchanges: { count: 1, concurrency: 1 },
        symbols: { count: 1, concurrency: 2 },
      };

      const queueNames = Object.keys(handlerMapping);
      this.logger.debug('Using known queue names', { count: queueNames.length, names: queueNames });

      // Create BullMQ queues directly with the correct format
      for (const handlerName of queueNames) {
        try {
          // Import BullMQ directly to create queue instances
          const { Queue: BullMQQueue } = await import('bullmq');
          const connection = {
            host: 'localhost',
            port: 6379,
            db: 0, // All queues now in DB 0
          };

          // Get the service that owns this handler
          const serviceName = handlerMapping[handlerName as keyof typeof handlerMapping];

          // Create BullMQ queue with the new naming format {service_handler}
          const fullQueueName = `{${serviceName}_${handlerName}}`;
          const bullQueue = new BullMQQueue(fullQueueName, { connection });

          // Get stats directly from BullMQ
          const queueStats = await this.getQueueStatsForBullQueue(bullQueue, handlerName);

          // Get actual worker count from BullMQ
          const actualWorkerCount = await this.getActiveWorkerCountFromBullMQ(bullQueue);
          const configuredWorkers = workerConfig[handlerName] || { count: 0, concurrency: 1 };

          stats.push({
            name: handlerName,
            service: serviceName,
            queue: handlerName,
            connected: true,
            jobs: queueStats,
            workers: {
              count: actualWorkerCount,
              concurrency: configuredWorkers.concurrency,
            },
          });

          // Close the queue connection after getting stats
          await bullQueue.close();
        } catch (error) {
          this.logger.warn(`Failed to get stats for queue ${handlerName}`, { error });
          stats.push({
            name: handlerName,
            service: serviceName,
            queue: handlerName,
            connected: false,
            jobs: {
              waiting: 0,
              active: 0,
              completed: 0,
              failed: 0,
              delayed: 0,
              paused: 0,
            },
          });
        }
      }
    } catch (error) {
      this.logger.error('Failed to get queue stats', { error });
    }

    return stats;
  }

  /**
   * Get stats for a BullMQ queue directly
   */
  private async getQueueStatsForBullQueue(bullQueue: any, queueName: string) {
    try {
      // BullMQ provides getJobCounts which returns all counts at once
      const counts = await bullQueue.getJobCounts();

      return {
        waiting: counts.waiting || 0,
        active: counts.active || 0,
        completed: counts.completed || 0,
        failed: counts.failed || 0,
        delayed: counts.delayed || 0,
        paused: counts.paused || 0,
        prioritized: counts.prioritized || 0,
        'waiting-children': counts['waiting-children'] || 0,
      };
    } catch (error) {
      this.logger.error(`Failed to get stats for BullMQ queue ${queueName}`, { error });
      // Fallback to individual methods
      try {
        const [waiting, active, completed, failed, delayed, paused] = await Promise.all([
          bullQueue.getWaitingCount(),
          bullQueue.getActiveCount(),
          bullQueue.getCompletedCount(),
          bullQueue.getFailedCount(),
          bullQueue.getDelayedCount(),
          bullQueue.getPausedCount ? bullQueue.getPausedCount() : 0,
        ]);

        return {
          waiting,
          active,
          completed,
          failed,
          delayed,
          paused,
        };
      } catch (fallbackError) {
        this.logger.error(`Fallback also failed for queue ${queueName}`, { fallbackError });
        return this.getQueueStatsForQueue(bullQueue, queueName);
      }
    }
  }

  /**
   * Get stats for a specific queue
   */
  private async getQueueStatsForQueue(queue: any, _queueName: string) {
    // Check if it has the getStats method
    if (queue.getStats && typeof queue.getStats === 'function') {
      const stats = await queue.getStats();
      return {
        waiting: stats.waiting || 0,
        active: stats.active || 0,
        completed: stats.completed || 0,
        failed: stats.failed || 0,
        delayed: stats.delayed || 0,
        paused: stats.paused || 0,
      };
    }

    // Try individual count methods
    const [waiting, active, completed, failed, delayed] = await Promise.all([
      this.safeGetCount(queue, 'getWaitingCount', 'getWaiting'),
      this.safeGetCount(queue, 'getActiveCount', 'getActive'),
      this.safeGetCount(queue, 'getCompletedCount', 'getCompleted'),
      this.safeGetCount(queue, 'getFailedCount', 'getFailed'),
      this.safeGetCount(queue, 'getDelayedCount', 'getDelayed'),
    ]);

    const paused = await this.safeGetPausedStatus(queue);

    return {
      waiting,
      active,
      completed,
      failed,
      delayed,
      paused,
    };
  }

  /**
   * Safely get count from queue
   */
  private async safeGetCount(queue: any, ...methodNames: string[]): Promise<number> {
    for (const methodName of methodNames) {
      if (queue[methodName] && typeof queue[methodName] === 'function') {
        try {
          const result = await queue[methodName]();
          return Array.isArray(result) ? result.length : result || 0;
        } catch (_e) {
          // Continue to next method
        }
      }
    }
    return 0;
  }

  /**
   * Get paused status
   */
  private async safeGetPausedStatus(queue: any): Promise<number> {
    try {
      if (queue.isPaused && typeof queue.isPaused === 'function') {
        const isPaused = await queue.isPaused();
        return isPaused ? 1 : 0;
      }
      if (queue.getPausedCount && typeof queue.getPausedCount === 'function') {
        return await queue.getPausedCount();
      }
    } catch (_e) {
      // Ignore
    }
    return 0;
  }

  /**
   * Get worker info for a queue
   */
  private getWorkerInfo(queue: any, queueManager: any, _queueName: string) {
    try {
      // Check queue itself for worker info
      if (queue.workers && Array.isArray(queue.workers)) {
        return {
          count: queue.workers.length,
          concurrency: queue.workers[0]?.concurrency || 1,
        };
      }

      // Check queue manager for worker config
      if (queueManager.config?.defaultQueueOptions) {
        const options = queueManager.config.defaultQueueOptions;
        return {
          count: options.workers || 1,
          concurrency: options.concurrency || 1,
        };
      }

      // Check for getWorkerCount method
      if (queue.getWorkerCount && typeof queue.getWorkerCount === 'function') {
        const count = queue.getWorkerCount();
        return {
          count,
          concurrency: 1,
        };
      }
    } catch (_e) {
      // Ignore
    }

    return undefined;
  }

  /**
   * Get database statistics
   */
  async getDatabaseStats(): Promise<DatabaseStats[]> {
    const stats: DatabaseStats[] = [];

    // PostgreSQL stats
    if (this.container.postgres) {
      try {
        const startTime = Date.now();
        const _result = await this.container.postgres.query('SELECT 1');
        const latency = Date.now() - startTime;

        // Get pool stats
        const pool = (this.container.postgres as any).pool;
        const poolStats = pool
          ? {
              size: pool.totalCount || 0,
              active: pool.idleCount || 0,
              idle: pool.waitingCount || 0,
              max: pool.options?.max || 0,
            }
          : undefined;

        stats.push({
          type: 'postgres',
          name: 'PostgreSQL',
          connected: true,
          latency,
          pool: poolStats,
        });
      } catch (error) {
        this.logger.error('Failed to get PostgreSQL stats', { error });
        stats.push({
          type: 'postgres',
          name: 'PostgreSQL',
          connected: false,
        });
      }
    }

    // MongoDB stats
    if (this.container.mongodb) {
      try {
        const startTime = Date.now();
        const mongoClient = this.container.mongodb as any; // This is MongoDBClient
        const db = mongoClient.getDatabase();
        await db.admin().ping();
        const latency = Date.now() - startTime;

        const serverStatus = await db.admin().serverStatus();

        stats.push({
          type: 'mongodb',
          name: 'MongoDB',
          connected: true,
          latency,
          stats: {
            version: serverStatus.version,
            uptime: serverStatus.uptime,
            connections: serverStatus.connections,
            opcounters: serverStatus.opcounters,
          },
        });
      } catch (error) {
        this.logger.error('Failed to get MongoDB stats', { error });
        stats.push({
          type: 'mongodb',
          name: 'MongoDB',
          connected: false,
        });
      }
    }

    // QuestDB stats
    if (this.container.questdb) {
      try {
        const startTime = Date.now();
        // QuestDB health check
        const response = await fetch(
          `http://${process.env.QUESTDB_HOST || 'localhost'}:9000/exec?query=SELECT%201`
        );
        const latency = Date.now() - startTime;

        stats.push({
          type: 'questdb',
          name: 'QuestDB',
          connected: response.ok,
          latency,
        });
      } catch (error) {
        this.logger.error('Failed to get QuestDB stats', { error });
        stats.push({
          type: 'questdb',
          name: 'QuestDB',
          connected: false,
        });
      }
    }

    return stats;
  }

  /**
   * Get system health summary
   */
  async getSystemHealth(): Promise<SystemHealth> {
    const [cacheStats, queueStats, databaseStats] = await Promise.all([
      this.getCacheStats(),
      this.getQueueStats(),
      this.getDatabaseStats(),
    ]);

    const processMemory = process.memoryUsage();
    const systemMemory = this.getSystemMemory();
    const uptime = Date.now() - this.startTime;
    const cpuInfo = this.getCpuUsage();

    // Determine overall health status
    const errors: string[] = [];

    if (!cacheStats.connected) {
      errors.push('Cache service is disconnected');
    }

    const disconnectedQueues = queueStats.filter(q => !q.connected);
    if (disconnectedQueues.length > 0) {
      errors.push(`${disconnectedQueues.length} queue(s) are disconnected`);
    }

    const disconnectedDbs = databaseStats.filter(db => !db.connected);
    if (disconnectedDbs.length > 0) {
      errors.push(`${disconnectedDbs.length} database(s) are disconnected`);
    }

    const status = errors.length === 0 ? 'healthy' : errors.length < 3 ? 'degraded' : 'unhealthy';

    return {
      status,
      timestamp: new Date().toISOString(),
      uptime,
      memory: {
        used: systemMemory.used,
        total: systemMemory.total,
        percentage: systemMemory.percentage,
        heap: {
          used: processMemory.heapUsed,
          total: processMemory.heapTotal,
        },
      },
      cpu: cpuInfo,
      services: {
        cache: cacheStats,
        queues: queueStats,
        databases: databaseStats,
      },
      errors: errors.length > 0 ? errors : undefined,
    };
  }

  /**
   * Get service metrics (placeholder for future implementation)
   */
  async getServiceMetrics(): Promise<ServiceMetrics> {
    const now = new Date().toISOString();

    return {
      requestsPerSecond: {
        timestamp: now,
        value: 0,
        unit: 'req/s',
      },
      averageResponseTime: {
        timestamp: now,
        value: 0,
        unit: 'ms',
      },
      errorRate: {
        timestamp: now,
        value: 0,
        unit: '%',
      },
      activeConnections: {
        timestamp: now,
        value: 0,
        unit: 'connections',
      },
    };
  }

  /**
   * Parse value from Redis INFO output
   */
  private parseInfoValue(info: string, key: string): number {
    const match = info.match(new RegExp(`${key}:(\\d+)`));
    return match ? parseInt(match[1], 10) : 0;
  }

  /**
   * Parse Redis INFO into structured object
   */
  private parseRedisInfo(info: string): Record<string, any> {
    const result: Record<string, any> = {};
    const sections = info.split('\r\n\r\n');

    for (const section of sections) {
      const lines = section.split('\r\n');
      const sectionName = lines[0]?.replace('# ', '') || 'general';
      result[sectionName] = {};

      for (let i = 1; i < lines.length; i++) {
        const [key, value] = lines[i].split(':');
        if (key && value) {
          result[sectionName][key] = value;
        }
      }
    }

    return result;
  }

  /**
   * Get service status for all microservices
   */
  async getServiceStatus(): Promise<ServiceStatus[]> {
    const services: ServiceStatus[] = [];

    // Define service endpoints
    const serviceEndpoints = [
      { name: 'data-ingestion', port: 2001, path: '/health' },
      { name: 'data-pipeline', port: 2002, path: '/health' },
      { name: 'web-api', port: 2003, path: '/health' },
    ];

    for (const service of serviceEndpoints) {
      try {
        // For the current service (web-api), add it directly without health check
        if (service.name === 'web-api') {
          services.push({
            name: 'web-api',
            version: '1.0.0',
            status: 'running',
            port: process.env.PORT ? parseInt(process.env.PORT) : 2003,
            uptime: Date.now() - this.startTime,
            lastCheck: new Date().toISOString(),
            healthy: true,
          });
          continue;
        }

        const startTime = Date.now();
        const response = await fetch(`http://localhost:${service.port}${service.path}`, {
          signal: AbortSignal.timeout(5000), // 5 second timeout
        });
        const _latency = Date.now() - startTime;

        if (response.ok) {
          const data = await response.json();
          services.push({
            name: service.name,
            version: data.version || '1.0.0',
            status: 'running',
            port: service.port,
            uptime: data.uptime || 0,
            lastCheck: new Date().toISOString(),
            healthy: true,
          });
        } else {
          services.push({
            name: service.name,
            version: 'unknown',
            status: 'error',
            port: service.port,
            uptime: 0,
            lastCheck: new Date().toISOString(),
            healthy: false,
            error: `HTTP ${response.status}`,
          });
        }
      } catch (error) {
        services.push({
          name: service.name,
          version: 'unknown',
          status: 'stopped',
          port: service.port,
          uptime: 0,
          lastCheck: new Date().toISOString(),
          healthy: false,
          error: error instanceof Error ? error.message : 'Connection failed',
        });
      }
    }

    return services;
  }

  /**
   * Get proxy statistics
   */
  async getProxyStats(): Promise<ProxyStats | null> {
    try {
      // Since web-api doesn't have proxy manager, query the cache directly
      // The proxy manager stores data with cache:proxy: prefix
      if (!this.container.cache) {
        return {
          enabled: false,
          totalProxies: 0,
          workingProxies: 0,
          failedProxies: 0,
        };
      }

      try {
        // Get proxy data from cache using getRaw method
        // The proxy manager uses cache:proxy: prefix, but web-api cache uses cache:api:
        const cacheProvider = this.container.cache;

        if (cacheProvider.getRaw) {
          // Use getRaw to access data with different cache prefix
          // The proxy manager now uses a global cache:proxy: prefix
          this.logger.debug('Attempting to fetch proxy data from cache');

          const [cachedProxies, lastUpdateStr] = await Promise.all([
            cacheProvider.getRaw<any[]>('cache:proxy:active'),
            cacheProvider.getRaw<string>('cache:proxy:last-update'),
          ]);

          this.logger.debug('Proxy cache data retrieved', {
            hasProxies: !!cachedProxies,
            isArray: Array.isArray(cachedProxies),
            proxyCount: cachedProxies ? cachedProxies.length : 0,
            lastUpdate: lastUpdateStr,
          });

          if (cachedProxies && Array.isArray(cachedProxies)) {
            const workingCount = cachedProxies.filter((p: any) => p.isWorking !== false).length;
            const failedCount = cachedProxies.filter((p: any) => p.isWorking === false).length;

            return {
              enabled: true,
              totalProxies: cachedProxies.length,
              workingProxies: workingCount,
              failedProxies: failedCount,
              lastUpdate: lastUpdateStr || undefined,
            };
          }
        } else {
          this.logger.debug('Cache provider does not support getRaw method');
        }

        // No cached data found - proxies might not be initialized yet
        return {
          enabled: true,
          totalProxies: 0,
          workingProxies: 0,
          failedProxies: 0,
        };
      } catch (cacheError) {
        this.logger.debug('Could not retrieve proxy data from cache', { error: cacheError });

        // Return basic stats if cache query fails
        return {
          enabled: true,
          totalProxies: 0,
          workingProxies: 0,
          failedProxies: 0,
        };
      }
    } catch (error) {
      this.logger.error('Failed to get proxy stats', { error });
      return null;
    }
  }

  /**
   * Get comprehensive system overview
   */
  async getSystemOverview(): Promise<SystemOverview> {
    const [services, health, proxies] = await Promise.all([
      this.getServiceStatus(),
      this.getSystemHealth(),
      this.getProxyStats(),
    ]);

    return {
      services,
      health,
      proxies: proxies || undefined,
      environment: {
        nodeVersion: process.version,
        platform: os.platform(),
        architecture: os.arch(),
        hostname: os.hostname(),
      },
    };
  }

  /**
   * Get detailed CPU usage
   */
  private getCpuUsage() {
    const cpus = os.cpus();
    let totalIdle = 0;
    let totalTick = 0;

    cpus.forEach(cpu => {
      for (const type in cpu.times) {
        totalTick += cpu.times[type as keyof typeof cpu.times];
      }
      totalIdle += cpu.times.idle;
    });

    const idle = totalIdle / cpus.length;
    const total = totalTick / cpus.length;
    const usage = 100 - ~~((100 * idle) / total);

    return {
      usage,
      loadAverage: os.loadavg(),
      cores: cpus.length,
    };
  }

  /**
   * Get system memory info
   */
  private getSystemMemory() {
    const totalMem = os.totalmem();
    const freeMem = os.freemem();

    // On Linux, freeMem includes buffers/cache, but we want "available" memory
    // which better represents memory that can be used by applications
    let availableMem = freeMem;

    // Try to read from /proc/meminfo for more accurate memory stats on Linux
    if (os.platform() === 'linux') {
      try {
        const fs = require('fs');
        const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
        const lines = meminfo.split('\n');

        let memAvailable = 0;
        let _memTotal = 0;

        for (const line of lines) {
          if (line.startsWith('MemAvailable:')) {
            memAvailable = parseInt(line.split(/\s+/)[1], 10) * 1024; // Convert from KB to bytes
          } else if (line.startsWith('MemTotal:')) {
            _memTotal = parseInt(line.split(/\s+/)[1], 10) * 1024;
          }
        }

        if (memAvailable > 0) {
          availableMem = memAvailable;
        }
      } catch (error) {
        // Fallback to os.freemem() if we can't read /proc/meminfo
        this.logger.debug('Could not read /proc/meminfo', { error });
      }
    }

    const usedMem = totalMem - availableMem;

    return {
      total: totalMem,
      used: usedMem,
      free: freeMem,
      available: availableMem,
      percentage: (usedMem / totalMem) * 100,
    };
  }

  /**
   * Get active worker count from Redis
   */
  /**
   * Get active worker count using BullMQ's built-in tracking
   */
  private async getActiveWorkerCountFromBullMQ(bullQueue: any): Promise<number> {
    try {
      // Use BullMQ's built-in getWorkers method
      if (bullQueue.getWorkers && typeof bullQueue.getWorkers === 'function') {
        const workers = await bullQueue.getWorkers();
        return workers.length;
      }

      // Fallback to getWorkersCount if available
      if (bullQueue.getWorkersCount && typeof bullQueue.getWorkersCount === 'function') {
        return await bullQueue.getWorkersCount();
      }

      return 0;
    } catch (error) {
      this.logger.debug('Failed to get active worker count from BullMQ', { error });
      return 0;
    }
  }
}
