import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';

export interface HealthStatus {
  status: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: Date;
  uptime: number;
  services: {
    database: ServiceHealth;
    redis: ServiceHealth;
    mongodb: ServiceHealth;
    openai?: ServiceHealth;
    storage?: ServiceHealth;
  };
  metrics: {
    memoryUsage: {
      total: number;
      used: number;
      free: number;
      percentUsed: number;
    };
    cpuUsage?: number;
    requestsPerMinute?: number;
    averageResponseTime?: number;
  };
}

export interface ServiceHealth {
  status: 'up' | 'down' | 'degraded';
  responseTime?: number;
  lastCheck: Date;
  error?: string;
  metadata?: Record<string, any>;
}

@Injectable()
export class HealthCheckService {
  private readonly logger = new Logger(HealthCheckService.name);
  private startTime: Date;
  private requestCount = 0;
  private responseTimes: number[] = [];

  constructor(
    @InjectDataSource() private dataSource: DataSource,
    private configService: ConfigService,
  ) {
    this.startTime = new Date();
  }

  /**
   * Get comprehensive health status
   */
  async getHealthStatus(): Promise<HealthStatus> {
    const [database, redis, mongodb, openai] = await Promise.all([
      this.checkDatabase(),
      this.checkRedis(),
      this.checkMongoDB(),
      this.checkOpenAI(),
    ]);

    const memoryUsage = process.memoryUsage();
    const totalMemory = memoryUsage.heapTotal;
    const usedMemory = memoryUsage.heapUsed;

    const overallStatus = this.determineOverallStatus([
      database,
      redis,
      mongodb,
    ]);

    return {
      status: overallStatus,
      timestamp: new Date(),
      uptime: Date.now() - this.startTime.getTime(),
      services: {
        database,
        redis,
        mongodb,
        openai,
      },
      metrics: {
        memoryUsage: {
          total: totalMemory,
          used: usedMemory,
          free: totalMemory - usedMemory,
          percentUsed: (usedMemory / totalMemory) * 100,
        },
        requestsPerMinute: this.calculateRequestsPerMinute(),
        averageResponseTime: this.calculateAverageResponseTime(),
      },
    };
  }

  /**
   * Simple health check for load balancers
   */
  async isHealthy(): Promise<boolean> {
    try {
      const dbHealth = await this.checkDatabase();
      return dbHealth.status === 'up';
    } catch {
      return false;
    }
  }

  /**
   * Check database connectivity
   */
  private async checkDatabase(): Promise<ServiceHealth> {
    const startTime = Date.now();

    try {
      await this.dataSource.query('SELECT 1');

      return {
        status: 'up',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        metadata: {
          type: 'postgresql',
          poolSize: this.dataSource.options['poolSize'] || 'default',
        },
      };
    } catch (error) {
      this.logger.error('Database health check failed', error.stack);
      return {
        status: 'down',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        error: error.message,
      };
    }
  }

  /**
   * Check Redis connectivity
   */
  private async checkRedis(): Promise<ServiceHealth> {
    const startTime = Date.now();

    try {
      // Placeholder - would use actual Redis client
      // const redisClient = this.redisService.getClient();
      // await redisClient.ping();

      return {
        status: 'up',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        metadata: {
          type: 'redis',
        },
      };
    } catch (error) {
      this.logger.error('Redis health check failed', error.stack);
      return {
        status: 'down',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        error: error.message,
      };
    }
  }

  /**
   * Check MongoDB connectivity
   */
  private async checkMongoDB(): Promise<ServiceHealth> {
    const startTime = Date.now();

    try {
      // Placeholder - would use actual MongoDB client
      // const mongoClient = this.mongoService.getClient();
      // await mongoClient.db().admin().ping();

      return {
        status: 'up',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        metadata: {
          type: 'mongodb',
        },
      };
    } catch (error) {
      this.logger.error('MongoDB health check failed', error.stack);
      return {
        status: 'down',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        error: error.message,
      };
    }
  }

  /**
   * Check OpenAI API connectivity
   */
  private async checkOpenAI(): Promise<ServiceHealth> {
    const startTime = Date.now();
    const apiKey = this.configService.get('OPENAI_API_KEY');

    if (!apiKey) {
      return {
        status: 'degraded',
        responseTime: 0,
        lastCheck: new Date(),
        error: 'API key not configured',
      };
    }

    try {
      // Placeholder - would make actual API call
      // const openai = new OpenAI({ apiKey });
      // await openai.models.list();

      return {
        status: 'up',
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        metadata: {
          type: 'openai',
        },
      };
    } catch (error) {
      this.logger.warn('OpenAI health check failed', error.message);
      return {
        status: 'degraded', // AI failure is degraded, not critical
        responseTime: Date.now() - startTime,
        lastCheck: new Date(),
        error: error.message,
      };
    }
  }

  /**
   * Determine overall system status
   */
  private determineOverallStatus(
    services: ServiceHealth[],
  ): 'healthy' | 'degraded' | 'unhealthy' {
    const criticalServices = services.filter((s) =>
      ['database', 'redis'].includes(s.metadata?.type),
    );

    const hasDownCritical = criticalServices.some((s) => s.status === 'down');
    const hasDegraded = services.some((s) => s.status === 'degraded');

    if (hasDownCritical) return 'unhealthy';
    if (hasDegraded) return 'degraded';
    return 'healthy';
  }

  /**
   * Track request metrics
   */
  trackRequest(responseTime: number): void {
    this.requestCount++;
    this.responseTimes.push(responseTime);

    // Keep only last 1000 response times to prevent memory issues
    if (this.responseTimes.length > 1000) {
      this.responseTimes.shift();
    }
  }

  /**
   * Calculate requests per minute
   */
  private calculateRequestsPerMinute(): number {
    const uptimeMinutes = (Date.now() - this.startTime.getTime()) / 60000;
    return this.requestCount / Math.max(uptimeMinutes, 1);
  }

  /**
   * Calculate average response time
   */
  private calculateAverageResponseTime(): number {
    if (this.responseTimes.length === 0) return 0;

    const sum = this.responseTimes.reduce((acc, time) => acc + time, 0);
    return sum / this.responseTimes.length;
  }

  /**
   * Get detailed metrics for monitoring dashboard
   */
  async getDetailedMetrics(): Promise<{
    uptime: number;
    requests: {
      total: number;
      perMinute: number;
    };
    performance: {
      avgResponseTime: number;
      p95ResponseTime: number;
      p99ResponseTime: number;
    };
    memory: {
      heapUsed: number;
      heapTotal: number;
      external: number;
      rss: number;
    };
    database: {
      activeConnections?: number;
      poolSize?: number;
    };
  }> {
    const mem = process.memoryUsage();
    const sortedTimes = [...this.responseTimes].sort((a, b) => a - b);

    return {
      uptime: Date.now() - this.startTime.getTime(),
      requests: {
        total: this.requestCount,
        perMinute: this.calculateRequestsPerMinute(),
      },
      performance: {
        avgResponseTime: this.calculateAverageResponseTime(),
        p95ResponseTime: this.getPercentile(sortedTimes, 95),
        p99ResponseTime: this.getPercentile(sortedTimes, 99),
      },
      memory: {
        heapUsed: mem.heapUsed,
        heapTotal: mem.heapTotal,
        external: mem.external,
        rss: mem.rss,
      },
      database: {
        // Would be populated from actual connection pool
        activeConnections: undefined,
        poolSize: undefined,
      },
    };
  }

  /**
   * Calculate percentile from sorted array
   */
  private getPercentile(sortedArray: number[], percentile: number): number {
    if (sortedArray.length === 0) return 0;

    const index = Math.ceil((percentile / 100) * sortedArray.length) - 1;
    return sortedArray[Math.max(0, index)];
  }

  /**
   * Reset metrics (useful for testing)
   */
  resetMetrics(): void {
    this.requestCount = 0;
    this.responseTimes = [];
    this.startTime = new Date();
  }
}
