/**
 * Application Metrics
 * Agent 4: Production Deployment
 *
 * Provides application metrics for Prometheus monitoring.
 * Tracks request counts, response times, and application health.
 */

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

interface MetricValue {
  value: number;
  labels: Record<string, string>;
  timestamp: number;
}

interface HistogramBucket {
  le: number;
  count: number;
}

interface Histogram {
  buckets: HistogramBucket[];
  sum: number;
  count: number;
  labels: Record<string, string>;
}

/**
 * Simple in-memory metrics store
 * In production, replace with prom-client for full Prometheus compatibility
 */
class MetricsRegistry {
  private counters: Map<string, MetricValue[]> = new Map();
  private gauges: Map<string, MetricValue> = new Map();
  private histograms: Map<string, Histogram[]> = new Map();
  private readonly defaultBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];

  /**
   * Increment a counter metric
   */
  incrementCounter(name: string, labels: Record<string, string> = {}, value = 1): void {
    const key = this.getKey(name, labels);
    const existing = this.counters.get(key) || [];

    // Find existing entry with same labels or create new
    const labelKey = JSON.stringify(labels);
    const existingEntry = existing.find(e => JSON.stringify(e.labels) === labelKey);

    if (existingEntry) {
      existingEntry.value += value;
      existingEntry.timestamp = Date.now();
    } else {
      existing.push({ value, labels, timestamp: Date.now() });
      this.counters.set(key, existing);
    }
  }

  /**
   * Set a gauge metric value
   */
  setGauge(name: string, value: number, labels: Record<string, string> = {}): void {
    const key = this.getKey(name, labels);
    this.gauges.set(key, { value, labels, timestamp: Date.now() });
  }

  /**
   * Increment a gauge metric
   */
  incrementGauge(name: string, labels: Record<string, string> = {}, value = 1): void {
    const key = this.getKey(name, labels);
    const existing = this.gauges.get(key);
    const newValue = (existing?.value || 0) + value;
    this.gauges.set(key, { value: newValue, labels, timestamp: Date.now() });
  }

  /**
   * Decrement a gauge metric
   */
  decrementGauge(name: string, labels: Record<string, string> = {}, value = 1): void {
    this.incrementGauge(name, labels, -value);
  }

  /**
   * Observe a value in a histogram
   */
  observeHistogram(name: string, value: number, labels: Record<string, string> = {}): void {
    const key = this.getKey(name, labels);
    let histograms = this.histograms.get(key);

    if (!histograms) {
      histograms = [];
      this.histograms.set(key, histograms);
    }

    const labelKey = JSON.stringify(labels);
    let histogram = histograms.find(h => JSON.stringify(h.labels) === labelKey);

    if (!histogram) {
      histogram = {
        buckets: this.defaultBuckets.map(le => ({ le, count: 0 })),
        sum: 0,
        count: 0,
        labels,
      };
      histograms.push(histogram);
    }

    // Update histogram
    histogram.sum += value;
    histogram.count += 1;

    for (const bucket of histogram.buckets) {
      if (value <= bucket.le) {
        bucket.count += 1;
      }
    }
  }

  /**
   * Get all metrics in Prometheus format
   */
  toPrometheusFormat(): string {
    const lines: string[] = [];
    const prefix = 'lgc';

    // Counters
    for (const [name, values] of this.counters.entries()) {
      const metricName = `${prefix}_${name.replace(/[^a-zA-Z0-9_]/g, '_')}_total`;
      lines.push(`# HELP ${metricName} Counter metric`);
      lines.push(`# TYPE ${metricName} counter`);

      for (const v of values) {
        const labelStr = this.formatLabels(v.labels);
        lines.push(`${metricName}${labelStr} ${v.value}`);
      }
    }

    // Gauges
    const gaugeGroups = new Map<string, MetricValue[]>();
    for (const [key, value] of this.gauges.entries()) {
      const name = key.split('|')[0];
      if (!gaugeGroups.has(name)) {
        gaugeGroups.set(name, []);
      }
      gaugeGroups.get(name)!.push(value);
    }

    for (const [name, values] of gaugeGroups.entries()) {
      const metricName = `${prefix}_${name.replace(/[^a-zA-Z0-9_]/g, '_')}`;
      lines.push(`# HELP ${metricName} Gauge metric`);
      lines.push(`# TYPE ${metricName} gauge`);

      for (const v of values) {
        const labelStr = this.formatLabels(v.labels);
        lines.push(`${metricName}${labelStr} ${v.value}`);
      }
    }

    // Histograms
    for (const [name, histograms] of this.histograms.entries()) {
      const metricName = `${prefix}_${name.replace(/[^a-zA-Z0-9_]/g, '_')}`;
      lines.push(`# HELP ${metricName} Histogram metric`);
      lines.push(`# TYPE ${metricName} histogram`);

      for (const h of histograms) {
        const baseLabels = this.formatLabels(h.labels);

        for (const bucket of h.buckets) {
          const bucketLabel = h.labels ? `,le="${bucket.le}"` : `le="${bucket.le}"`;
          const labelStr = baseLabels ? baseLabels.slice(0, -1) + bucketLabel + '}' : `{${bucketLabel.slice(1)}}`;
          lines.push(`${metricName}_bucket${labelStr} ${bucket.count}`);
        }

        const infLabel = h.labels ? baseLabels.slice(0, -1) + `,le="+Inf"}` : `{le="+Inf"}`;
        lines.push(`${metricName}_bucket${infLabel} ${h.count}`);
        lines.push(`${metricName}_sum${baseLabels} ${h.sum}`);
        lines.push(`${metricName}_count${baseLabels} ${h.count}`);
      }
    }

    return lines.join('\n');
  }

  /**
   * Get metrics as JSON
   */
  toJSON(): Record<string, unknown> {
    return {
      counters: Object.fromEntries(this.counters),
      gauges: Object.fromEntries(this.gauges),
      histograms: Object.fromEntries(this.histograms),
      timestamp: new Date().toISOString(),
    };
  }

  /**
   * Reset all metrics
   */
  reset(): void {
    this.counters.clear();
    this.gauges.clear();
    this.histograms.clear();
  }

  private getKey(name: string, labels: Record<string, string>): string {
    return name;
  }

  private formatLabels(labels: Record<string, string>): string {
    const entries = Object.entries(labels);
    if (entries.length === 0) return '';
    return '{' + entries.map(([k, v]) => `${k}="${v}"`).join(',') + '}';
  }
}

// Create singleton instance
export const metrics = new MetricsRegistry();

// Pre-defined metric helpers
export const httpMetrics = {
  requestTotal(method: string, path: string, statusCode: number): void {
    metrics.incrementCounter('http_requests', {
      method,
      path,
      status: String(statusCode),
    });
  },

  requestDuration(method: string, path: string, durationMs: number): void {
    metrics.observeHistogram('http_request_duration_seconds', durationMs / 1000, {
      method,
      path,
    });
  },

  activeConnections(delta: number): void {
    metrics.incrementGauge('http_active_connections', {}, delta);
  },
};

export const appMetrics = {
  plantsRegistered(count = 1): void {
    metrics.incrementCounter('plants_registered', {}, count);
  },

  transportEvents(eventType: string, count = 1): void {
    metrics.incrementCounter('transport_events', { type: eventType }, count);
  },

  agentCycleCompleted(agentName: string, durationMs: number): void {
    metrics.observeHistogram('agent_cycle_duration_seconds', durationMs / 1000, {
      agent: agentName,
    });
    metrics.incrementCounter('agent_cycles', { agent: agentName });
  },

  activeAgents(count: number): void {
    metrics.setGauge('active_agents', count);
  },

  blockchainBlocks(count: number): void {
    metrics.setGauge('blockchain_blocks', count);
  },

  databaseConnections(count: number): void {
    metrics.setGauge('database_connections', count);
  },
};

// Export types
export type { MetricValue, Histogram, HistogramBucket };
