import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
import { ManagedStorageService } from './infrastructure/storage/managed-storage.service';
import { RedisService } from './infrastructure/redis/redis.service';
import { ShutdownCoordinatorService } from './infrastructure/reliability/shutdown-coordinator.service';
import { NotificationServiceClient } from './modules/notifications/notification-service.client';

@Injectable()
export class AppService {
  constructor(
    @InjectConnection() private readonly connection: Connection,
    private readonly redis: RedisService,
    private readonly storage: ManagedStorageService,
    private readonly shutdownCoordinator: ShutdownCoordinatorService,
    private readonly notificationClient: NotificationServiceClient,
  ) {}

  getHealth(): { status: string; service: string } {
    return { status: 'ok', service: 'oudelaa-backend' };
  }

  async getReadiness(): Promise<Record<string, unknown>> {
    const checks: Record<string, { status: 'up' | 'down'; error?: string }> = {};
    if (this.shutdownCoordinator.isDraining()) {
      checks.lifecycle = { status: 'down', error: 'Application is draining for shutdown' };
      return {
        status: 'draining',
        service: 'oudelaa-backend',
        checks,
        activeRequests: this.shutdownCoordinator.getActiveRequests(),
      };
    }
    checks.lifecycle = { status: 'up' };
    try {
      if (!this.connection.db) throw new Error('MongoDB connection is unavailable');
      await this.withTimeout(this.connection.db.admin().ping(), 3000, 'MongoDB ping timeout');
      checks.mongodb = { status: 'up' };
    } catch (error) {
      checks.mongodb = { status: 'down', error: this.errorMessage(error) };
    }

    if (this.redis.isEnabled()) {
      try {
        const client = this.redis.getClient();
        if (!client) throw new Error('Redis client is unavailable');
        await this.withTimeout(client.ping(), 2000, 'Redis ping timeout');
        checks.redis = { status: 'up' };
      } catch (error) {
        checks.redis = { status: 'down', error: this.errorMessage(error) };
      }
    } else {
      checks.redis = { status: 'up' };
    }

    checks.rabbitmq = this.notificationClient.isReady()
      ? { status: 'up' }
      : { status: 'down', error: 'RabbitMQ connection is unavailable' };

    try {
      const health = await this.withTimeout(this.storage.getHealth(), 5000, 'Storage check timeout');
      const local = health.local as { readable?: boolean; writable?: boolean } | undefined;
      const s3 = health.s3 as { reachable?: boolean; configured?: boolean } | undefined;
      const storageUp = health.provider === 'local'
        ? local?.readable === true && local?.writable === true
        : s3?.configured === true && s3?.reachable === true;
      checks.storage = storageUp
        ? { status: 'up' }
        : { status: 'down', error: 'Storage is not readable/writable or reachable' };
    } catch (error) {
      checks.storage = { status: 'down', error: this.errorMessage(error) };
    }

    const ready = Object.values(checks).every((check) => check.status === 'up');
    return { status: ready ? 'ready' : 'degraded', service: 'oudelaa-backend', checks };
  }

  private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
    let timer: NodeJS.Timeout | undefined;
    try {
      return await Promise.race([
        promise,
        new Promise<T>((_, reject) => { timer = setTimeout(() => reject(new Error(message)), timeoutMs); }),
      ]);
    } finally {
      if (timer) clearTimeout(timer);
    }
  }

  private errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : 'Unknown dependency error';
  }
}
