/**
 * GitLab Data Acquisition Service
 *
 * This service provides functionality to fetch text content (specifically llms.txt files)
 * from GitLab URLs using HTTP GET requests with proper error handling and authentication.
 */

import { config } from '../config.js';

/**
 * Custom error class for GitLab-specific errors
 */
export class GitLabError extends Error {
  constructor(
    message: string,
    public readonly statusCode?: number,
    public readonly url?: string
  ) {
    super(message);
    this.name = 'GitLabError';
  }
}

/**
 * Authentication error class for GitLab authentication failures
 */
export class GitLabAuthError extends GitLabError {
  constructor(message: string, url?: string) {
    super(message, 401, url);
    this.name = 'GitLabAuthError';
  }
}

/**
 * Creates authentication headers for GitLab requests
 *
 * @returns Record<string, string> - Headers object with authentication if token is available
 */
function createAuthHeaders(): Record<string, string> {
  const headers: Record<string, string> = {
    'User-Agent': 'awesome-components-mcp/1.0.0'
  };

  if (config.auth.gitlabToken) {
    headers['Authorization'] = `Bearer ${config.auth.gitlabToken}`;
    console.log('Using GitLab Personal Access Token for authentication');
  } else {
    console.log('No GitLab token provided, attempting public access');
  }

  return headers;
}

/**
 * Fetches text content from a GitLab URL with authentication support
 *
 * @param url - The GitLab URL to fetch content from
 * @returns Promise<string> - The fetched content as a string
 * @throws {GitLabError} - When the request fails or returns an error status
 * @throws {GitLabAuthError} - When authentication fails
 */
export async function fetchGitLabFileContent(url: string): Promise<string> {
  try {
    console.log(`Fetching content from GitLab URL: ${url}`);

    const headers = createAuthHeaders();
    const response = await fetch(url, { headers });

    if (!response.ok) {
      // Log detailed error for server-side diagnostics (without exposing sensitive info)
      console.error(`GitLab request failed: ${response.status} ${response.statusText}`);

      // Create specific error messages based on status codes
      let errorMessage: string;
      switch (response.status) {
        case 401:
          errorMessage = `Authentication failed (status: 401). Please check your GitLab Personal Access Token.`;
          throw new GitLabAuthError(errorMessage, url);
        case 403:
          if (config.auth.gitlabToken) {
            errorMessage = `Access forbidden (status: 403). Your token may not have sufficient permissions for this repository.`;
          } else {
            errorMessage = `Access forbidden (status: 403). This repository may be private and require authentication. Please provide a GitLab Personal Access Token.`;
          }
          break;
        case 404:
          errorMessage = `File not found (status: 404). Please check if the file exists and the path is correct.`;
          break;
        case 500:
        case 502:
        case 503:
        case 504:
          errorMessage = `GitLab server error (status: ${response.status}). Please try again later.`;
          break;
        default:
          errorMessage = `Failed to fetch content from GitLab (status: ${response.status}).`;
      }

      // Throw an error that can be caught by the tool and handled by MCP SDK
      throw new GitLabError(errorMessage, response.status, url);
    }

    const content = await response.text();
    console.log(`Successfully fetched ${content.length} characters from GitLab`);

    return content;

  } catch (error) {
    // If it's already a GitLabError or GitLabAuthError, re-throw it
    if (error instanceof GitLabError) {
      throw error;
    }

    // Handle network errors and other fetch-related errors
    console.error(`Network or other error fetching from GitLab:`, error);

    // Create a more user-friendly error message for network issues
    const networkError = new GitLabError(
      `Network error while fetching from GitLab: ${error instanceof Error ? error.message : 'Unknown error'}`,
      undefined,
      url
    );

    throw networkError;
  }
}

/**
 * Validates if a URL appears to be a valid GitLab URL
 * 
 * @param url - The URL to validate
 * @returns boolean - True if the URL appears to be a valid GitLab URL
 */
export function isValidGitLabUrl(url: string): boolean {
  try {
    const urlObj = new URL(url);
    
    // Check if it's a valid HTTP/HTTPS URL
    if (!['http:', 'https:'].includes(urlObj.protocol)) {
      return false;
    }
    
    // Check if it contains GitLab-like patterns
    const hostname = urlObj.hostname.toLowerCase();
    const pathname = urlObj.pathname.toLowerCase();
    
    // Common GitLab patterns - must contain 'gitlab' in hostname
    const isGitLabHostname = hostname.includes('gitlab');
    const hasRawPath = pathname.includes('/-/raw/');
    
    return isGitLabHostname && hasRawPath;
    
  } catch {
    return false;
  }
}

/**
 * Validates authentication configuration for private repositories
 *
 * @param url - The GitLab URL to check
 * @returns boolean - True if authentication is properly configured or not needed
 */
export function validateAuthenticationConfig(url: string): { isValid: boolean; message?: string } {
  // For public repositories, authentication is optional
  // For private repositories, authentication is required

  if (!config.auth.gitlabToken) {
    return {
      isValid: true,
      message: 'No authentication token provided. This will work for public repositories only.'
    };
  }

  // Basic token validation
  const token = config.auth.gitlabToken.trim();
  if (token.length < 10) {
    return {
      isValid: false,
      message: 'GitLab Personal Access Token appears to be invalid (too short).'
    };
  }

  return {
    isValid: true,
    message: 'Authentication token is configured.'
  };
}

/**
 * Fetches content with URL validation and authentication support
 *
 * @param url - The GitLab URL to fetch content from
 * @returns Promise<string> - The fetched content as a string
 * @throws {GitLabError} - When the URL is invalid or the request fails
 * @throws {GitLabAuthError} - When authentication fails
 */
export async function fetchGitLabFileContentSafe(url: string): Promise<string> {
  if (!isValidGitLabUrl(url)) {
    throw new GitLabError(
      `Invalid GitLab URL format. Expected a GitLab raw file URL.`,
      undefined,
      url
    );
  }

  // Validate authentication configuration
  const authValidation = validateAuthenticationConfig(url);
  if (!authValidation.isValid) {
    throw new GitLabAuthError(authValidation.message || 'Authentication configuration is invalid', url);
  }

  return fetchGitLabFileContent(url);
}
