/**
 * Git Input Validation Utility
 *
 * Provides strict validation for git references (branch names, repository URLs)
 * to prevent command injection vulnerabilities.
 *
 * Security: Whitelist-based approach - only allow known-safe characters.
 */

import { BadRequestException, Logger } from "@nestjs/common";

const logger = new Logger("GitValidation");

/**
 * Validates a git branch name for safety
 *
 * Allowed format: alphanumeric, hyphens, underscores, forward slashes
 * Examples: "main", "feature/add-login", "fix/bug_123"
 *
 * Rejected: Special characters that could be interpreted as git syntax
 * Examples: "--option", "$(command)", ";malicious", "`command`"
 *
 * @param branchName - The branch name to validate
 * @throws BadRequestException if branch name is invalid
 */
export function validateBranchName(branchName: string): void {
  // Check for empty or whitespace-only
  if (!branchName || branchName.trim().length === 0) {
    throw new BadRequestException("Branch name cannot be empty");
  }

  // Check length (git has a 255 char limit for ref names)
  if (branchName.length > 255) {
    throw new BadRequestException("Branch name exceeds maximum length (255 characters)");
  }

  // Whitelist: only allow alphanumeric, hyphens, underscores, forward slashes, dots
  // This prevents all forms of command injection
  const safePattern = /^[a-zA-Z0-9/_.-]+$/;
  if (!safePattern.test(branchName)) {
    logger.warn({
      event: "GIT_COMMAND_INJECTION_BLOCKED",
      input: branchName,
      reason: "Invalid characters detected",
      securityEvent: true,
      timestamp: new Date().toISOString(),
    });
    throw new BadRequestException(
      `Branch name contains invalid characters. Only alphanumeric, hyphens, underscores, slashes, and dots are allowed.`
    );
  }

  // Prevent git option injection (branch names starting with -)
  if (branchName.startsWith("-")) {
    logger.warn({
      event: "GIT_OPTION_INJECTION_BLOCKED",
      input: branchName,
      reason: "Branch name starts with hyphen (option injection attempt)",
      securityEvent: true,
      timestamp: new Date().toISOString(),
    });
    throw new BadRequestException(
      "Branch name cannot start with a hyphen (prevents option injection)"
    );
  }

  // Prevent double dots (used for range specifications in git)
  if (branchName.includes("..")) {
    logger.warn({
      event: "GIT_RANGE_INJECTION_BLOCKED",
      input: branchName,
      reason: "Double dots detected (git range specification)",
      securityEvent: true,
      timestamp: new Date().toISOString(),
    });
    throw new BadRequestException("Branch name cannot contain consecutive dots (..)");
  }

  // Prevent path traversal patterns
  if (branchName.includes("/../") || branchName.startsWith("../") || branchName.endsWith("/..")) {
    logger.warn({
      event: "GIT_PATH_TRAVERSAL_BLOCKED",
      input: branchName,
      reason: "Path traversal pattern detected",
      securityEvent: true,
      timestamp: new Date().toISOString(),
    });
    throw new BadRequestException("Branch name cannot contain path traversal patterns");
  }

  // Prevent ending with .lock (reserved by git)
  if (branchName.endsWith(".lock")) {
    throw new BadRequestException("Branch name cannot end with .lock (reserved by git)");
  }

  // Prevent control characters
  // eslint-disable-next-line no-control-regex
  if (/[\x00-\x1F\x7F]/.test(branchName)) {
    throw new BadRequestException("Branch name cannot contain control characters");
  }
}

/**
 * Validates a git repository URL for safety
 *
 * Allowed protocols: https, http (dev only), ssh (git@)
 * Prevents: file://, javascript:, data:, and other dangerous protocols
 *
 * @param repositoryUrl - The repository URL to validate
 * @throws BadRequestException if URL is invalid or uses dangerous protocol
 */
export function validateRepositoryUrl(repositoryUrl: string): void {
  // Check for empty or whitespace-only
  if (!repositoryUrl || repositoryUrl.trim().length === 0) {
    throw new BadRequestException("Repository URL cannot be empty");
  }

  // Check length (reasonable limit for URLs)
  if (repositoryUrl.length > 2000) {
    throw new BadRequestException("Repository URL exceeds maximum length (2000 characters)");
  }

  // Remove whitespace
  const url = repositoryUrl.trim();

  // Whitelist allowed protocols
  const httpsPattern = /^https:\/\//i;
  const httpPattern = /^http:\/\//i; // Only for development
  const sshGitPattern = /^git@[a-zA-Z0-9.-]+:/; // git@host:repo format
  const sshUrlPattern = /^ssh:\/\/git@[a-zA-Z0-9.-]+(\/|:)/; // ssh://git@host/repo or ssh://git@host:repo

  if (
    !httpsPattern.test(url) &&
    !httpPattern.test(url) &&
    !sshGitPattern.test(url) &&
    !sshUrlPattern.test(url) &&
    !url.startsWith("git://")
  ) {
    throw new BadRequestException(
      "Repository URL must use https://, http://, ssh://, git://, or git@ protocol"
    );
  }

  // Prevent dangerous protocols
  const dangerousProtocols = [
    "file://",
    "javascript:",
    "data:",
    "vbscript:",
    "about:",
    "chrome:",
    "view-source:",
  ];

  for (const dangerous of dangerousProtocols) {
    if (url.toLowerCase().startsWith(dangerous)) {
      logger.warn({
        event: "GIT_DANGEROUS_PROTOCOL_BLOCKED",
        input: url,
        protocol: dangerous,
        reason: `Dangerous protocol detected: ${dangerous}`,
        securityEvent: true,
        timestamp: new Date().toISOString(),
      });
      throw new BadRequestException(
        `Repository URL cannot use ${dangerous} protocol (security risk)`
      );
    }
  }

  // Prevent localhost/internal network access (SSRF protection)
  const localhostPatterns = [
    /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|::1)/i,
    /https?:\/\/192\.168\./i,
    /https?:\/\/10\./i,
    /https?:\/\/172\.(1[6-9]|2\d|3[01])\./i,
  ];

  for (const pattern of localhostPatterns) {
    if (pattern.test(url)) {
      logger.warn({
        event: "GIT_SSRF_ATTEMPT_BLOCKED",
        input: url,
        reason: "Repository URL points to localhost or internal network",
        securityEvent: true,
        timestamp: new Date().toISOString(),
      });
      throw new BadRequestException(
        "Repository URL cannot point to localhost or internal networks (SSRF protection)"
      );
    }
  }

  // Prevent credential injection in URL
  if (url.includes("@") && !sshGitPattern.test(url) && !sshUrlPattern.test(url)) {
    // Extract the part before @ to check if it looks like credentials
    const beforeAt = url.split("@")[0];
    if (beforeAt.includes("://") && beforeAt.split("://")[1].includes(":")) {
      throw new BadRequestException("Repository URL cannot contain embedded credentials");
    }
  }

  // Prevent control characters and dangerous characters in URL
  // eslint-disable-next-line no-control-regex
  if (/[\x00-\x1F\x7F`$;|&]/.test(url)) {
    throw new BadRequestException("Repository URL contains invalid or dangerous characters");
  }
}

/**
 * Validates a complete agent spawn context
 *
 * @param context - The spawn context with repository and branch
 * @throws BadRequestException if any field is invalid
 */
export function validateSpawnContext(context: { repository: string; branch: string }): void {
  validateRepositoryUrl(context.repository);
  validateBranchName(context.branch);
}
