/**
 * Metrics lookup utility for Google Cloud Monitoring
 * 
 * This module provides functionality to search and retrieve metric information
 * from the metrics documentation files based on natural language queries.
 */
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const readFile = promisify(fs.readFile);

/**
 * Represents a metric from the documentation
 */
export interface Metric {
  /** The full metric type (e.g., 'compute.googleapis.com/instance/cpu/utilization') */
  type: string;
  /** The display name of the metric */
  displayName: string;
  /** The description of the metric */
  description: string;
  /** The kind of metric (GAUGE, DELTA, CUMULATIVE) */
  kind: string;
  /** The value type (INT64, DOUBLE, DISTRIBUTION, etc.) */
  valueType: string;
  /** The unit of the metric */
  unit: string;
  /** The monitored resource types this metric applies to */
  monitoredResources: string[];
  /** The labels that can be used with this metric */
  labels: Array<{
    name: string;
    description: string;
  }>;
  /** The source documentation file this metric was found in */
  source: string;
}

/**
 * Categories of metrics documentation
 */
export enum MetricCategory {
  GCP = 'gcp',
  Kubernetes = 'kubernetes',
  Istio = 'istio'
}

/**
 * Class to handle metric lookups from documentation
 */
export class MetricsLookup {
  private metrics: Metric[] = [];
  private initialized = false;
  private metricsDir: string;

  /**
   * Create a new MetricsLookup instance
   * 
   * @param metricsDir The directory containing metrics markdown files
   */
  constructor(metricsDir: string) {
    this.metricsDir = metricsDir;
  }

  /**
   * Initialize the metrics lookup by parsing all metrics files
   */
  async initialize(): Promise<void> {
    if (this.initialized) {
      return;
    }

    try {
      // Parse all metrics files
      await this.parseMetricsFile(MetricCategory.GCP);
      await this.parseMetricsFile(MetricCategory.Kubernetes);
      await this.parseMetricsFile(MetricCategory.Istio);
      
      this.initialized = true;
      // Metrics lookup initialized successfully
    } catch (error) {
      // Failed to initialize metrics lookup
      throw error;
    }
  }

  /**
   * Parse a metrics markdown file and extract metric information
   * 
   * @param category The category of metrics to parse
   */
  private async parseMetricsFile(category: MetricCategory): Promise<void> {
    const filename = `metrics_${category}.md`;
    const filepath = path.join(this.metricsDir, filename);
    
    try {
      const content = await readFile(filepath, 'utf-8');
      
      // Extract metrics from the markdown content
      // This is a simplified implementation - in a real-world scenario,
      // we would use a more robust markdown parser
      
      // Split the content by metric entries (each starting with "* Metric type")
      const metricEntries = content.split(/\n\* Metric type/).slice(1);
      
      for (const entry of metricEntries) {
        try {
          // Extract metric type
          const typeMatch = entry.match(/([a-zA-Z0-9_/]+)\s+([A-Z]+)\s+\(project\)/);
          if (!typeMatch) continue;
          
          const metricName = typeMatch[1].trim();
          
          // Extract display name
          const displayNameMatch = entry.match(/Display name:\s+([^\n]+)/);
          const displayName = displayNameMatch ? displayNameMatch[1].trim() : '';
          
          // Extract kind, type, and unit
          const kindTypeUnitMatch = entry.match(/(GAUGE|DELTA|CUMULATIVE),\s+(INT64|DOUBLE|DISTRIBUTION|STRING),\s+([^\n]+)/);
          const kind = kindTypeUnitMatch ? kindTypeUnitMatch[1].trim() : '';
          const valueType = kindTypeUnitMatch ? kindTypeUnitMatch[2].trim() : '';
          const unit = kindTypeUnitMatch ? kindTypeUnitMatch[3].trim() : '';
          
          // Extract monitored resources
          const resourcesMatch = entry.match(/([a-zA-Z0-9_.]+(\s+[a-zA-Z0-9_.]+)*)/g);
          const monitoredResources = resourcesMatch ? 
            resourcesMatch.filter(r => r.includes('.googleapis.com') || 
              ['k8s_container', 'k8s_pod', 'istio_canonical_service'].includes(r)) : [];
          
          // Extract description
          const descriptionMatch = entry.match(/\*\s+(.*?)(?=\s+[a-zA-Z_]+:|\s*$)/s);
          const description = descriptionMatch ? descriptionMatch[1].trim() : '';
          
          // Extract labels
          const labels: Array<{ name: string; description: string }> = [];
          const labelMatches = entry.matchAll(/([a-zA-Z_]+):\s+(.*?)(?=\s+[a-zA-Z_]+:|\s*$)/g);
          
          for (const match of labelMatches) {
            labels.push({
              name: match[1].trim(),
              description: match[2].trim()
            });
          }
          
          // Determine the full metric type based on category
          let fullType = '';
          if (category === MetricCategory.GCP) {
            // For GCP metrics, we need to find the API prefix
            const apiPrefixMatch = content.match(/The "metric type" strings in this table must be prefixed with `([^`]+)`/);
            const apiPrefix = apiPrefixMatch ? apiPrefixMatch[1] : '';
            fullType = apiPrefix + metricName;
          } else if (category === MetricCategory.Kubernetes) {
            fullType = `kubernetes.io/${metricName}`;
          } else if (category === MetricCategory.Istio) {
            fullType = `istio.io/${metricName}`;
          }
          
          this.metrics.push({
            type: fullType,
            displayName,
            description,
            kind,
            valueType,
            unit,
            monitoredResources,
            labels,
            source: category
          });
        } catch (error) {
          // Error parsing metric entry - skipping
          // Continue with next entry
        }
      }
      
      // Metrics file parsed successfully
    } catch (error) {
      // Error reading metrics file - skipping
      // Don't throw here, just log the error and continue
    }
  }

  /**
   * Find metrics based on a natural language query
   * 
   * @param query The natural language query to search for
   * @param category Optional category to limit the search to
   * @param limit Maximum number of results to return
   * @returns Array of matching metrics
   */
  findMetrics(query: string, category?: MetricCategory, limit = 5): Metric[] {
    if (!this.initialized) {
      throw new Error('Metrics lookup not initialized. Call initialize() first.');
    }

    // Convert query to lowercase for case-insensitive matching
    const lowerQuery = query.toLowerCase();
    
    // Extract key terms from the query
    const terms = this.extractKeyTerms(lowerQuery);
    
    // Score each metric based on how well it matches the query
    const scoredMetrics = this.metrics
      .filter(metric => !category || metric.source === category)
      .map(metric => {
        const score = this.calculateRelevanceScore(metric, terms, lowerQuery);
        return { metric, score };
      })
      .filter(item => item.score > 0)
      .sort((a, b) => b.score - a.score);
    
    // Return the top N results
    return scoredMetrics.slice(0, limit).map(item => item.metric);
  }

  /**
   * Extract key terms from a natural language query
   * 
   * @param query The query to extract terms from
   * @returns Array of key terms
   */
  private extractKeyTerms(query: string): string[] {
    // Remove common words and split into terms
    const stopWords = ['a', 'an', 'the', 'and', 'or', 'but', 'is', 'are', 'for', 'with', 'about', 'to', 'in', 'of'];
    
    return query
      .toLowerCase()
      .replace(/[^\w\s]/g, ' ')
      .split(/\s+/)
      .filter(word => word.length > 2 && !stopWords.includes(word));
  }

  /**
   * Calculate a relevance score for a metric based on how well it matches the query terms
   * 
   * @param metric The metric to score
   * @param terms The key terms from the query
   * @param fullQuery The full original query
   * @returns A relevance score (higher is better)
   */
  private calculateRelevanceScore(metric: Metric, terms: string[], fullQuery: string): number {
    let score = 0;
    
    // Check for exact matches in the metric type (highest priority)
    if (metric.type.toLowerCase().includes(fullQuery)) {
      score += 100;
    }
    
    // Check for exact matches in the display name
    if (metric.displayName.toLowerCase().includes(fullQuery)) {
      score += 80;
    }
    
    // Check for exact matches in the description
    if (metric.description.toLowerCase().includes(fullQuery)) {
      score += 60;
    }
    
    // Check for individual term matches
    for (const term of terms) {
      // Match in type
      if (metric.type.toLowerCase().includes(term)) {
        score += 30;
      }
      
      // Match in display name
      if (metric.displayName.toLowerCase().includes(term)) {
        score += 25;
      }
      
      // Match in description
      if (metric.description.toLowerCase().includes(term)) {
        score += 20;
      }
      
      // Match in labels
      for (const label of metric.labels) {
        if (label.name.toLowerCase().includes(term) || 
            label.description.toLowerCase().includes(term)) {
          score += 15;
        }
      }
    }
    
    return score;
  }

  /**
   * Get a metric by its exact type
   * 
   * @param metricType The full metric type to look up
   * @returns The metric or undefined if not found
   */
  getMetricByType(metricType: string): Metric | undefined {
    return this.metrics.find(m => m.type === metricType);
  }

  /**
   * Suggest a monitoring filter based on a natural language query
   * 
   * @param query The natural language query
   * @returns A suggested monitoring filter string
   */
  suggestFilter(query: string): string {
    const metrics = this.findMetrics(query);
    
    if (metrics.length === 0) {
      return '';
    }
    
    // Use the top matching metric to create a filter
    const topMetric = metrics[0];
    
    // Basic filter with just the metric type
    let filter = `metric.type="${topMetric.type}"`;
    
    // Try to extract additional filter conditions from the query
    const resourceMatch = /resource\s+(?:type|is|equals?)\s+["']?([a-zA-Z0-9_]+)["']?/i.exec(query);
    if (resourceMatch && resourceMatch[1]) {
      filter += ` AND resource.type="${resourceMatch[1]}"`;
    }
    
    // Look for label conditions
    for (const label of topMetric.labels) {
      const labelRegex = new RegExp(`${label.name}\\s+(?:is|equals?|=)\\s+["']?([\\w-]+)["']?`, 'i');
      const match = labelRegex.exec(query);
      
      if (match && match[1]) {
        filter += ` AND metric.labels.${label.name}="${match[1]}"`;
      }
    }
    
    return filter;
  }
}

// Create dirname equivalent for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Export a singleton instance
// Point directly to the directory containing the metrics markdown files
export const metricsLookup = new MetricsLookup(path.join(__dirname));
