import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Cron } from '@nestjs/schedule';
import * as net from 'net';
import { exec } from 'child_process';
import { promisify } from 'util';
import { RedisEventBus } from '@it0/events';
import { EventPatterns } from '@it0/common';
import { HealthCheckResultRepository } from '../../infrastructure/repositories/health-check-result.repository';
import { HealthCheckResult } from '../entities/health-check-result.entity';

const execAsync = promisify(exec);

interface ServerTarget {
  id: string;
  host: string;
  httpPort?: number;
  tcpPort?: number;
}

@Injectable()
export class HealthCheckService {
  private readonly logger = new Logger(HealthCheckService.name);
  private readonly probeTimeoutMs: number;

  constructor(
    private readonly healthCheckRepo: HealthCheckResultRepository,
    private readonly eventBus: RedisEventBus,
    private readonly configService: ConfigService,
  ) {
    this.probeTimeoutMs = this.configService.get<number>('HEALTH_CHECK_TIMEOUT_MS', 5000);
  }

  @Cron('*/5 * * * *')
  async runScheduledChecks(): Promise<void> {
    const serversJson = this.configService.get<string>('MONITORED_SERVERS', '[]');
    let servers: ServerTarget[];

    try {
      servers = JSON.parse(serversJson);
    } catch {
      this.logger.warn('Failed to parse MONITORED_SERVERS config, skipping scheduled checks');
      return;
    }

    this.logger.log(`Running scheduled health checks for ${servers.length} server(s)`);

    for (const server of servers) {
      try {
        await this.probeServer(server);
      } catch (error: any) {
        this.logger.error(`Health check failed for ${server.host}: ${error.message}`);
      }
    }
  }

  async probeServer(server: ServerTarget): Promise<HealthCheckResult> {
    const result = new HealthCheckResult();
    result.id = crypto.randomUUID();
    result.serverId = server.id;
    result.serverHost = server.host;
    result.checkedAt = new Date();
    result.createdAt = new Date();

    const checks = {
      ping: false,
      tcp: false,
      http: false,
    };

    // Ping check via ICMP (child_process)
    try {
      const startPing = Date.now();
      const isWindows = process.platform === 'win32';
      const pingCmd = isWindows
        ? `ping -n 1 -w ${this.probeTimeoutMs} ${server.host}`
        : `ping -c 1 -W ${Math.ceil(this.probeTimeoutMs / 1000)} ${server.host}`;

      await execAsync(pingCmd, { timeout: this.probeTimeoutMs + 2000 });
      result.latencyMs = Date.now() - startPing;
      checks.ping = true;
    } catch {
      this.logger.debug(`Ping failed for ${server.host}`);
    }

    // TCP check
    if (server.tcpPort) {
      try {
        await this.probeTcp(server.host, server.tcpPort);
        checks.tcp = true;
      } catch {
        this.logger.debug(`TCP check failed for ${server.host}:${server.tcpPort}`);
      }
    }

    // HTTP check
    if (server.httpPort) {
      try {
        await this.probeTcp(server.host, server.httpPort);
        checks.http = true;
      } catch {
        this.logger.debug(`HTTP check failed for ${server.host}:${server.httpPort}`);
      }
    }

    // Determine overall status
    if (checks.ping && (checks.tcp || !server.tcpPort) && (checks.http || !server.httpPort)) {
      result.status = 'healthy';
      result.checkType = 'ping';
    } else if (checks.ping) {
      result.status = 'degraded';
      result.checkType = server.tcpPort ? 'tcp' : 'http';
      result.error = 'Host reachable but service check failed';
    } else {
      result.status = 'down';
      result.checkType = 'ping';
      result.error = 'Host unreachable';
    }

    await this.healthCheckRepo.save(result);

    // Publish event if server is down
    if (result.status === 'down') {
      await this.eventBus.publish(EventPatterns.HEALTH_CHECK_FAILED, {
        id: crypto.randomUUID(),
        tenantId: result.tenantId ?? 'system',
        timestamp: new Date(),
        source: 'monitor-service',
        type: 'health.check.failed',
        payload: {
          serverId: server.id,
          serverHost: server.host,
          status: result.status,
          error: result.error,
          checkedAt: result.checkedAt,
        },
      });

      this.logger.warn(`Server ${server.host} (${server.id}) is DOWN`);
    }

    return result;
  }

  private probeTcp(host: string, port: number): Promise<void> {
    return new Promise((resolve, reject) => {
      const socket = new net.Socket();
      socket.setTimeout(this.probeTimeoutMs);

      socket.on('connect', () => {
        socket.destroy();
        resolve();
      });

      socket.on('timeout', () => {
        socket.destroy();
        reject(new Error(`TCP timeout connecting to ${host}:${port}`));
      });

      socket.on('error', (err) => {
        socket.destroy();
        reject(err);
      });

      socket.connect(port, host);
    });
  }
}
