import { API_CONFIG } from '../config/api';
import { authService } from './auth-service';
import { log } from '../utils/logger';
import type { Machine } from '../types';

/**
 * Result of machine availability check
 */
export interface MachineAvailabilityResult {
  available: boolean;
  hostname: string;
  port: number;
  responseTimeMs?: number;
  checkedAt: string;
}

/**
 * Request body for availability check
 */
interface AvailabilityCheckRequest {
  hostname: string;
  port: number;
}

const DEFAULT_PORTS = {
  RDP: 3389,
  SSH: 22,
} as const;

const OS_IDENTIFIER = {
  WINDOWS: 'windows',
} as const;

/**
 * Service for checking machine availability
 *
 * Performs machine availability checks via API server
 * to avoid network restrictions on the client side.
 */
export class MachineAvailabilityService {
  /**
   * Determine default port based on machine OS
   *
   * Simple logic: Windows → RDP (3389), everything else → SSH (22)
   */
  private static getDefaultPort(machine: Machine): number {
    const os = machine.os.toLowerCase();

    if (os.includes(OS_IDENTIFIER.WINDOWS)) {
      return DEFAULT_PORTS.RDP;
    }

    return DEFAULT_PORTS.SSH;
  }

  /**
   * Create an unavailable result object
   */
  private static createUnavailableResult(
    hostname: string,
    port: number
  ): MachineAvailabilityResult {
    return {
      available: false,
      hostname,
      port,
      checkedAt: new Date().toISOString(),
    };
  }

  /**
   * Check machine availability
   *
   * @param machine - Machine to check
   * @param port - Port to check (optional, determined by OS)
   * @returns Availability check result
   */
  static async checkAvailability(
    machine: Machine,
    port?: number
  ): Promise<MachineAvailabilityResult> {
    try {
      const targetPort = port || this.getDefaultPort(machine);

      log.info('machine-availability', 'Checking machine availability', {
        machineId: machine.id,
        hostname: machine.name,
        os: machine.os,
        port: targetPort,
        portSource: port ? 'explicit' : 'auto-detected',
      });

      const headers = await authService.getAuthHeaders();

      const requestBody: AvailabilityCheckRequest = {
        hostname: machine.name,
        port: targetPort,
      };

      const response = await fetch(
        `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.MACHINES.CHECK_AVAILABILITY}`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            ...headers,
          },
          body: JSON.stringify(requestBody),
        }
      );

      if (!response.ok) {
        const errorText = await response.text().catch(() => 'Unknown error');
        log.error('machine-availability', 'Check failed', {
          hostname: machine.name,
          status: response.status,
          error: errorText,
        });

        return this.createUnavailableResult(machine.name, targetPort);
      }

      const data: MachineAvailabilityResult = await response.json();

      log.info('machine-availability', 'Check completed', {
        machineId: machine.id,
        hostname: data.hostname,
        available: data.available,
        responseTimeMs: data.responseTimeMs,
      });

      return data;
    } catch (error) {
      const targetPort = port || this.getDefaultPort(machine);

      log.error('machine-availability', 'Check failed with exception', {
        hostname: machine.name,
        error,
      });

      return this.createUnavailableResult(machine.name, targetPort);
    }
  }

  /**
   * Check availability of multiple machines simultaneously
   *
   * @param machines - Array of machines to check
   * @param port - Port to check (optional)
   * @returns Map with check results (machineId -> result)
   */
  static async checkMultiple(
    machines: Machine[],
    port?: number
  ): Promise<Map<string, MachineAvailabilityResult>> {
    const results = new Map<string, MachineAvailabilityResult>();

    const promises = machines.map(async (machine) => {
      const result = await this.checkAvailability(machine, port);
      return { machineId: machine.id, result };
    });

    const completed = await Promise.allSettled(promises);

    completed.forEach((promise) => {
      if (promise.status === 'fulfilled') {
        results.set(promise.value.machineId, promise.value.result);
      }
    });

    return results;
  }
}

export const machineAvailabilityService = MachineAvailabilityService;

