import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { RedisService } from '@core/cache/redis/redis.service';
import { TemporalProbeService } from './temporal-probe.service';

export interface HealthStatus {
  status: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: string;
  uptime: number;
  environment: string;
  version?: string;
}

export interface DetailedHealthStatus extends HealthStatus {
  services: {
    database: ServiceStatus;
    redis: ServiceStatus;
    temporal: ServiceStatus;
  };
  system: {
    memory: {
      used: string;
      total: string;
      percentage: number;
    };
    cpu: {
      usage: number;
    };
  };
}

export interface ServiceStatus {
  status: 'up' | 'down' | 'degraded';
  responseTime?: number;
  message?: string;
}

@Injectable()
export class HealthService {
  private readonly logger = new Logger(HealthService.name);
  private readonly startTime = Date.now();
  private readonly redisTimeoutMs = 1000;

  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: ConfigService,
    private readonly redisService: RedisService,
    private readonly temporalProbe: TemporalProbeService,
  ) {}

  async check(): Promise<HealthStatus> {
    return {
      status: 'healthy',
      timestamp: new Date().toISOString(),
      uptime: Math.floor((Date.now() - this.startTime) / 1000),
      environment: this.configService.get('NODE_ENV', 'development'),
      version: this.configService.get('APP_VERSION', '1.0.0'),
    };
  }

  async detailedCheck(): Promise<DetailedHealthStatus> {
    const [databaseStatus, redisStatus, temporalStatus] = await Promise.all([
      this.checkDatabase(),
      this.checkRedis(),
      this.checkTemporal(),
    ]);

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

    const overallStatus = this.aggregateStatus([databaseStatus, redisStatus, temporalStatus]);

    return {
      status: overallStatus,
      timestamp: new Date().toISOString(),
      uptime: Math.floor((Date.now() - this.startTime) / 1000),
      environment: this.configService.get('NODE_ENV', 'development'),
      version: this.configService.get('APP_VERSION', '1.0.0'),
      services: {
        database: databaseStatus,
        redis: redisStatus,
        temporal: temporalStatus,
      },
      system: {
        memory: {
          used: this.formatBytes(usedMemory),
          total: this.formatBytes(totalMemory),
          percentage: Math.round((usedMemory / totalMemory) * 100),
        },
        cpu: {
          usage: Math.round(process.cpuUsage().user / 1000000),
        },
      },
    };
  }

  async readinessCheck(): Promise<{ ready: boolean; message?: string }> {
    try {
      await this.prisma.$queryRaw`SELECT 1`;
      return { ready: true };
    } catch (error) {
      this.logger.error('Readiness check failed:', error);
      return {
        ready: false,
        message: 'Database connection failed',
      };
    }
  }

  async livenessCheck(): Promise<{ alive: boolean }> {
    return { alive: true };
  }

  private aggregateStatus(parts: ServiceStatus[]): 'healthy' | 'degraded' | 'unhealthy' {
    if (parts.some((s) => s.status === 'down')) return 'unhealthy';
    if (parts.some((s) => s.status === 'degraded')) return 'degraded';
    return 'healthy';
  }

  private async checkDatabase(): Promise<ServiceStatus> {
    const startTime = Date.now();
    try {
      await this.prisma.$queryRaw`SELECT 1`;
      const responseTime = Date.now() - startTime;
      if (responseTime > 1000) {
        return { status: 'degraded', responseTime, message: 'Database response time is slow' };
      }
      return { status: 'up', responseTime };
    } catch (error: any) {
      this.logger.error('Database health check failed:', error);
      return { status: 'down', message: error.message };
    }
  }

  private async checkRedis(): Promise<ServiceStatus> {
    const startTime = Date.now();
    let timer: NodeJS.Timeout | undefined;
    try {
      const timeoutPromise = new Promise<never>((_, reject) => {
        timer = setTimeout(
          () => reject(new Error(`Redis PING timeout (${this.redisTimeoutMs}ms)`)),
          this.redisTimeoutMs,
        );
      });
      await Promise.race([this.redisService.ping(), timeoutPromise]);
      return { status: 'up', responseTime: Date.now() - startTime };
    } catch (error: any) {
      this.logger.warn(`Redis health check failed: ${error.message}`);
      return { status: 'down', message: error.message };
    } finally {
      if (timer) clearTimeout(timer);
    }
  }

  private async checkTemporal(): Promise<ServiceStatus> {
    const result = await this.temporalProbe.probe();
    if (result.ok) {
      return { status: 'up', responseTime: result.latencyMs };
    }
    return { status: 'down', message: result.error };
  }

  private formatBytes(bytes: number): string {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
  }
}
