/**
 * HTTP Protocol Parser
 * Generic HTTP parsing utilities
 */

import { HTTP_METHODS, type THttpMethod, type THttpVersion } from './constants.js';
import type { IHttpRequestLine, IHttpHeader } from './types.js';

/**
 * HTTP parser utilities
 */
export class HttpParser {
  /**
   * Check if string is a valid HTTP method
   */
  static isHttpMethod(str: string): str is THttpMethod {
    return HTTP_METHODS.includes(str as THttpMethod);
  }
  
  /**
   * Parse HTTP request line
   */
  static parseRequestLine(line: string): IHttpRequestLine | null {
    const parts = line.trim().split(' ');
    
    if (parts.length !== 3) {
      return null;
    }
    
    const [method, path, version] = parts;
    
    // Validate method
    if (!this.isHttpMethod(method)) {
      return null;
    }
    
    // Validate version
    if (!version.startsWith('HTTP/')) {
      return null;
    }
    
    return {
      method: method as THttpMethod,
      path,
      version: version as THttpVersion
    };
  }
  
  /**
   * Parse HTTP header line
   */
  static parseHeaderLine(line: string): IHttpHeader | null {
    const colonIndex = line.indexOf(':');
    
    if (colonIndex === -1) {
      return null;
    }
    
    const name = line.slice(0, colonIndex).trim();
    const value = line.slice(colonIndex + 1).trim();
    
    if (!name) {
      return null;
    }
    
    return { name, value };
  }
  
  /**
   * Parse HTTP headers from lines
   */
  static parseHeaders(lines: string[]): Record<string, string> {
    const headers: Record<string, string> = {};
    
    for (const line of lines) {
      const header = this.parseHeaderLine(line);
      if (header) {
        // Convert header names to lowercase for consistency
        headers[header.name.toLowerCase()] = header.value;
      }
    }
    
    return headers;
  }
  
  /**
   * Extract domain from Host header value
   */
  static extractDomainFromHost(hostHeader: string): string {
    // Remove port if present
    const colonIndex = hostHeader.lastIndexOf(':');
    if (colonIndex !== -1) {
      // Check if it's not part of IPv6 address
      const beforeColon = hostHeader.slice(0, colonIndex);
      if (!beforeColon.includes(']')) {
        return beforeColon;
      }
    }
    return hostHeader;
  }
  
  /**
   * Validate domain name
   */
  static isValidDomain(domain: string): boolean {
    // Basic domain validation
    if (!domain || domain.length > 253) {
      return false;
    }
    
    // Check for valid characters and structure
    const domainRegex = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$/;
    return domainRegex.test(domain);
  }
  
  /**
   * Extract line from buffer
   */
  static extractLine(buffer: Buffer, offset: number = 0): { line: string; nextOffset: number } | null {
    // Look for CRLF
    const crlfIndex = buffer.indexOf('\r\n', offset);
    if (crlfIndex === -1) {
      // Look for just LF
      const lfIndex = buffer.indexOf('\n', offset);
      if (lfIndex === -1) {
        return null;
      }
      
      return {
        line: buffer.slice(offset, lfIndex).toString('utf8'),
        nextOffset: lfIndex + 1
      };
    }
    
    return {
      line: buffer.slice(offset, crlfIndex).toString('utf8'),
      nextOffset: crlfIndex + 2
    };
  }
  
  /**
   * Check if buffer contains printable ASCII
   */
  static isPrintableAscii(buffer: Buffer, length?: number): boolean {
    const checkLength = Math.min(length || buffer.length, buffer.length);
    
    for (let i = 0; i < checkLength; i++) {
      const byte = buffer[i];
      // Allow printable ASCII (32-126) plus tab (9), LF (10), and CR (13)
      if (byte < 32 || byte > 126) {
        if (byte !== 9 && byte !== 10 && byte !== 13) {
          return false;
        }
      }
    }
    
    return true;
  }
  
  /**
   * Quick check if buffer starts with HTTP method
   */
  static quickCheck(buffer: Buffer): boolean {
    if (buffer.length < 3) {
      return false;
    }
    
    // Check common HTTP methods
    const start = buffer.slice(0, 7).toString('ascii');
    return start.startsWith('GET ') ||
           start.startsWith('POST ') ||
           start.startsWith('PUT ') ||
           start.startsWith('DELETE ') ||
           start.startsWith('HEAD ') ||
           start.startsWith('OPTIONS') ||
           start.startsWith('PATCH ') ||
           start.startsWith('CONNECT') ||
           start.startsWith('TRACE ');
  }
  
  /**
   * Parse query string
   */
  static parseQueryString(queryString: string): Record<string, string> {
    const params: Record<string, string> = {};
    
    if (!queryString) {
      return params;
    }
    
    // Remove leading '?' if present
    if (queryString.startsWith('?')) {
      queryString = queryString.slice(1);
    }
    
    const pairs = queryString.split('&');
    for (const pair of pairs) {
      const [key, value] = pair.split('=');
      if (key) {
        params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : '';
      }
    }
    
    return params;
  }
  
  /**
   * Build query string from params
   */
  static buildQueryString(params: Record<string, string>): string {
    const pairs: string[] = [];
    
    for (const [key, value] of Object.entries(params)) {
      pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
    }
    
    return pairs.length > 0 ? '?' + pairs.join('&') : '';
  }
}