import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { GateRequirements } from "./types/gate-config.types";

/**
 * Request payload for quality check API
 */
export interface QualityCheckRequest {
  taskId: string;
  agentId: string;
  files: string[];
  diffSummary: string;
  gateRequirements?: GateRequirements;
}

/**
 * Response from coordinator quality check
 */
export interface QualityCheckResponse {
  approved: boolean;
  gate: string;
  message?: string;
  details?: Record<string, unknown>;
}

/**
 * Service for communicating with the coordinator's quality gate API
 */
@Injectable()
export class CoordinatorClientService {
  private readonly logger = new Logger(CoordinatorClientService.name);
  private readonly coordinatorUrl: string;
  private readonly timeout: number;
  private readonly maxRetries: number;
  private readonly apiKey: string | undefined;

  constructor(private readonly configService: ConfigService) {
    this.coordinatorUrl = this.configService.get<string>(
      "orchestrator.coordinator.url",
      "http://localhost:8000"
    );
    this.timeout = this.configService.get<number>("orchestrator.coordinator.timeout", 30000);
    this.maxRetries = this.configService.get<number>("orchestrator.coordinator.retries", 3);
    this.apiKey = this.configService.get<string>("orchestrator.coordinator.apiKey");

    // Security warnings for production
    const nodeEnv = this.configService.get<string>("NODE_ENV", "development");
    const isProduction = nodeEnv === "production";

    if (!this.apiKey) {
      if (isProduction) {
        this.logger.warn(
          "SECURITY WARNING: COORDINATOR_API_KEY is not configured. " +
            "Inter-service communication with coordinator is unauthenticated. " +
            "Configure COORDINATOR_API_KEY environment variable for secure communication."
        );
      } else {
        this.logger.debug(
          "COORDINATOR_API_KEY not configured. " +
            "Inter-service authentication is disabled (acceptable for development)."
        );
      }
    }

    // HTTPS enforcement warning for production
    if (isProduction && this.coordinatorUrl.startsWith("http://")) {
      this.logger.warn(
        "SECURITY WARNING: Coordinator URL uses HTTP instead of HTTPS. " +
          "Inter-service communication is not encrypted. " +
          "Configure COORDINATOR_URL with HTTPS for secure communication in production."
      );
    }

    this.logger.log(
      `Coordinator client initialized: ${this.coordinatorUrl} (timeout: ${this.timeout.toString()}ms, retries: ${this.maxRetries.toString()}, auth: ${this.apiKey ? "enabled" : "disabled"})`
    );
  }

  /**
   * Check quality gates via coordinator API
   * @param request Quality check request parameters
   * @returns Quality check response with approval status
   * @throws Error if request fails after all retries or validation fails
   */
  async checkQuality(request: QualityCheckRequest): Promise<QualityCheckResponse> {
    // Validate request before sending
    this.validateRequest(request);

    const url = `${this.coordinatorUrl}/api/quality/check`;

    this.logger.debug(`Checking quality for task ${request.taskId} via coordinator`);

    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => {
        controller.abort();
      }, this.timeout);

      try {
        const response = await fetch(url, {
          method: "POST",
          headers: this.buildHeaders(),
          body: JSON.stringify(request),
          signal: controller.signal,
        });

        // Retry on 503 (Service Unavailable)
        if (response.status === 503) {
          this.logger.warn(
            `Coordinator unavailable (attempt ${attempt.toString()}/${this.maxRetries.toString()})`
          );
          lastError = new Error(
            `Coordinator quality check failed: ${response.status.toString()} ${response.statusText}`
          );

          if (attempt < this.maxRetries) {
            await this.delay(this.getBackoffDelay(attempt));
            continue;
          }
          throw lastError;
        }

        if (!response.ok) {
          throw new Error(
            `Coordinator quality check failed: ${response.status.toString()} ${response.statusText}`
          );
        }

        let data: unknown;
        try {
          data = await response.json();
        } catch {
          throw new Error("Failed to parse coordinator response");
        }

        // Validate response structure
        if (!this.isValidQualityCheckResponse(data)) {
          throw new Error("Invalid coordinator response");
        }

        this.logger.log(
          `Quality check ${data.approved ? "approved" : "rejected"} for task ${request.taskId} (gate: ${data.gate})`
        );

        return data;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        // Don't retry on validation errors or non-503 HTTP errors
        if (
          lastError.message.includes("Invalid coordinator response") ||
          lastError.message.includes("Failed to parse") ||
          (lastError.message.includes("failed:") && !lastError.message.includes("503"))
        ) {
          throw lastError;
        }

        this.logger.warn(
          `Quality check attempt ${attempt.toString()}/${this.maxRetries.toString()} failed: ${lastError.message}`
        );

        if (attempt < this.maxRetries) {
          await this.delay(this.getBackoffDelay(attempt));
        } else {
          throw lastError;
        }
      } finally {
        clearTimeout(timeoutId);
      }
    }

    throw lastError ?? new Error("Quality check failed after all retries");
  }

  /**
   * Check if coordinator service is healthy
   * @returns true if coordinator is healthy, false otherwise
   */
  async isHealthy(): Promise<boolean> {
    const url = `${this.coordinatorUrl}/health`;
    const controller = new AbortController();
    const timeoutId = setTimeout(() => {
      controller.abort();
    }, 5000);

    try {
      const response = await fetch(url, {
        headers: this.buildHeaders(),
        signal: controller.signal,
      });

      return response.ok;
    } catch (error) {
      this.logger.warn(
        `Coordinator health check failed: ${error instanceof Error ? error.message : String(error)}`
      );
      return false;
    } finally {
      clearTimeout(timeoutId);
    }
  }

  /**
   * Type guard to validate quality check response structure
   */
  private isValidQualityCheckResponse(data: unknown): data is QualityCheckResponse {
    if (typeof data !== "object" || data === null) {
      return false;
    }

    const response = data as Record<string, unknown>;

    return typeof response.approved === "boolean" && typeof response.gate === "string";
  }

  /**
   * Build request headers including authentication if configured
   * @returns Headers object with Content-Type and optional X-API-Key
   */
  private buildHeaders(): Record<string, string> {
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
    };

    if (this.apiKey) {
      headers["X-API-Key"] = this.apiKey;
    }

    return headers;
  }

  /**
   * Calculate exponential backoff delay
   */
  private getBackoffDelay(attempt: number): number {
    // Exponential backoff: 1s, 2s, 4s
    return Math.min(1000 * Math.pow(2, attempt - 1), 5000);
  }

  /**
   * Delay helper for retries
   */
  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * Validate QualityCheckRequest to prevent security issues
   * @param request Request to validate
   * @throws Error if validation fails
   */
  private validateRequest(request: QualityCheckRequest): void {
    // Validate taskId
    if (!request.taskId || request.taskId.trim() === "") {
      throw new Error("taskId cannot be empty");
    }

    // Validate agentId
    if (!request.agentId || request.agentId.trim() === "") {
      throw new Error("agentId cannot be empty");
    }

    // Validate files array
    if (request.files.length === 0) {
      throw new Error("files array cannot be empty");
    }

    if (request.files.length > 1000) {
      throw new Error("files array exceeds maximum size of 1000");
    }

    // Validate each file path
    for (const filePath of request.files) {
      // Check for path traversal attempts
      if (filePath.includes("..")) {
        throw new Error(`file path contains path traversal: ${filePath}`);
      }

      // Check for null bytes
      if (filePath.includes("\0")) {
        throw new Error(`file path contains invalid characters: ${filePath}`);
      }

      // Check for absolute paths (should be relative)
      if (filePath.startsWith("/") || filePath.startsWith("\\")) {
        throw new Error(`file path must be relative: ${filePath}`);
      }

      // Check for Windows absolute paths (C:\, D:\, etc.)
      if (/^[a-zA-Z]:[/\\]/.test(filePath)) {
        throw new Error(`file path must be relative: ${filePath}`);
      }
    }

    // Validate diffSummary size (max 10KB)
    const diffSummaryBytes = new TextEncoder().encode(request.diffSummary).length;
    if (diffSummaryBytes > 10 * 1024) {
      throw new Error("diffSummary exceeds maximum size of 10KB");
    }
  }
}
