/**
 * Lightweight In-Memory Request Metrics
 *
 * Provides request monitoring separate from logging.
 * Use logs for debugging what happened; use metrics to know WHEN things happen.
 *
 * Exposes Prometheus-compatible text format via the staff dashboard API.
 * Metrics reset when the process restarts (stateless — not persisted).
 *
 * For production, pair with Prometheus scraping or push to a metrics backend.
 */

import type { RequestCategory } from './request-logger';

// ---------------------------------------------------------------------------
// Counter
// ---------------------------------------------------------------------------

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

class Counter {
  readonly name: string;
  readonly help: string;
  private entries: CounterEntry[] = [];

  constructor(name: string, help: string) {
    this.name = name;
    this.help = help;
  }

  inc(labels: Record<string, string>, value = 1): void {
    const existing = this.entries.find((e) =>
      Object.entries(labels).every(([k, v]) => e.labels[k] === v)
    );
    if (existing) {
      existing.value += value;
    } else {
      this.entries.push({ labels: { ...labels }, value });
    }
  }

  getEntries(): CounterEntry[] {
    return [...this.entries];
  }

  toPrometheus(): string {
    const lines = [`# HELP ${this.name} ${this.help}`, `# TYPE ${this.name} counter`];
    for (const entry of this.entries) {
      const labelStr = Object.entries(entry.labels)
        .map(([k, v]) => `${k}="${v}"`)
        .join(',');
      lines.push(`${this.name}{${labelStr}} ${entry.value}`);
    }
    return lines.join('\n');
  }
}

// ---------------------------------------------------------------------------
// Histogram (simplified — stores recent values for percentile calculation)
// ---------------------------------------------------------------------------

class Histogram {
  readonly name: string;
  readonly help: string;
  private buckets: Map<string, number[]> = new Map();
  private readonly maxSamples: number;

  constructor(name: string, help: string, maxSamples = 10000) {
    this.name = name;
    this.help = help;
    this.maxSamples = maxSamples;
  }

  observe(labels: Record<string, string>, value: number): void {
    const key = Object.entries(labels)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k}=${v}`)
      .join(',');

    let bucket = this.buckets.get(key);
    if (!bucket) {
      bucket = [];
      this.buckets.set(key, bucket);
    }

    bucket.push(value);

    // Ring buffer: drop oldest when full
    if (bucket.length > this.maxSamples) {
      bucket.splice(0, bucket.length - this.maxSamples);
    }
  }

  getPercentiles(labels: Record<string, string>): { p50: number; p95: number; p99: number; count: number; avg: number } | null {
    const key = Object.entries(labels)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k}=${v}`)
      .join(',');

    const bucket = this.buckets.get(key);
    if (!bucket || bucket.length === 0) return null;

    const sorted = [...bucket].sort((a, b) => a - b);
    const count = sorted.length;
    const sum = sorted.reduce((a, b) => a + b, 0);

    return {
      p50: sorted[Math.floor(count * 0.5)] ?? 0,
      p95: sorted[Math.floor(count * 0.95)] ?? 0,
      p99: sorted[Math.floor(count * 0.99)] ?? 0,
      count,
      avg: Math.round(sum / count),
    };
  }

  getAllBucketKeys(): string[] {
    return [...this.buckets.keys()];
  }

  toPrometheus(): string {
    const lines = [`# HELP ${this.name} ${this.help}`, `# TYPE ${this.name} histogram`];
    for (const [key, bucket] of this.buckets.entries()) {
      if (bucket.length === 0) continue;
      const sorted = [...bucket].sort((a, b) => a - b);
      const count = sorted.length;
      const sum = sorted.reduce((a, b) => a + b, 0);

      lines.push(`${this.name}_count{${key}} ${count}`);
      lines.push(`${this.name}_sum{${key}} ${Math.round(sum)}`);
      lines.push(`${this.name}_p50{${key}} ${sorted[Math.floor(count * 0.5)] ?? 0}`);
      lines.push(`${this.name}_p95{${key}} ${sorted[Math.floor(count * 0.95)] ?? 0}`);
      lines.push(`${this.name}_p99{${key}} ${sorted[Math.floor(count * 0.99)] ?? 0}`);
    }
    return lines.join('\n');
  }
}

// ---------------------------------------------------------------------------
// Global Metrics Registry
// ---------------------------------------------------------------------------

/** Total HTTP requests by method, path category, and status */
export const httpRequestsTotal = new Counter(
  'http_requests_total',
  'Total HTTP requests by method, category, and status code'
);

/** HTTP request duration in milliseconds */
export const httpRequestDuration = new Histogram(
  'http_request_duration_ms',
  'HTTP request duration in milliseconds by category'
);

/** Error count by category */
export const httpErrorsTotal = new Counter(
  'http_errors_total',
  'Total HTTP errors (4xx and 5xx) by category'
);

/** Log entries by level */
export const logEntriesTotal = new Counter(
  'log_entries_total',
  'Total log entries by level'
);

/** Active requests gauge (approximation) */
let activeRequests = 0;
const processStartTime = Date.now();

// ---------------------------------------------------------------------------
// Recording Functions
// ---------------------------------------------------------------------------

/**
 * Record a completed HTTP request.
 * Call this once per request from the canonical log line.
 */
export function recordRequest(params: {
  method: string;
  category: RequestCategory;
  statusCode: number;
  durationMs: number;
}): void {
  const { method, category, statusCode, durationMs } = params;
  const statusGroup = `${Math.floor(statusCode / 100)}xx`;

  httpRequestsTotal.inc({ method, category, status: statusGroup });
  httpRequestDuration.observe({ category }, durationMs);

  if (statusCode >= 400) {
    httpErrorsTotal.inc({ category, status: statusGroup });
  }
}

/**
 * Record a log entry for level distribution tracking.
 */
export function recordLogEntry(level: string): void {
  logEntriesTotal.inc({ level });
}

/**
 * Track active request count.
 */
export function requestStarted(): void {
  activeRequests++;
}

export function requestEnded(): void {
  activeRequests = Math.max(0, activeRequests - 1);
}

// ---------------------------------------------------------------------------
// Dashboard Data (JSON format for staff dashboard)
// ---------------------------------------------------------------------------

export interface MetricsDashboardData {
  uptime: {
    startedAt: string;
    uptimeSeconds: number;
  };
  activeRequests: number;
  requests: {
    total: CounterEntry[];
    errorRate: number;
  };
  responseTimes: {
    category: string;
    p50: number;
    p95: number;
    p99: number;
    count: number;
    avg: number;
  }[];
  errors: CounterEntry[];
  logLevels: CounterEntry[];
}

/**
 * Get all metrics as structured data for the staff dashboard.
 */
export function getDashboardMetrics(): MetricsDashboardData {
  const requestEntries = httpRequestsTotal.getEntries();
  const errorEntries = httpErrorsTotal.getEntries();

  // Calculate overall error rate
  const totalRequests = requestEntries.reduce((sum, e) => sum + e.value, 0);
  const totalErrors = errorEntries.reduce((sum, e) => sum + e.value, 0);
  const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;

  // Get response times per category
  const responseTimeBuckets = httpRequestDuration.getAllBucketKeys();
  const responseTimes = responseTimeBuckets
    .map((key) => {
      const labels: Record<string, string> = {};
      key.split(',').forEach((pair) => {
        const [k, v] = pair.split('=');
        if (k && v) labels[k] = v;
      });
      const percentiles = httpRequestDuration.getPercentiles(labels);
      if (!percentiles) return null;
      return {
        category: labels.category || 'unknown',
        ...percentiles,
      };
    })
    .filter((x): x is NonNullable<typeof x> => x !== null);

  return {
    uptime: {
      startedAt: new Date(processStartTime).toISOString(),
      uptimeSeconds: Math.floor((Date.now() - processStartTime) / 1000),
    },
    activeRequests,
    requests: {
      total: requestEntries,
      errorRate: Math.round(errorRate * 10000) / 100, // percentage with 2 decimals
    },
    responseTimes,
    errors: errorEntries,
    logLevels: logEntriesTotal.getEntries(),
  };
}

/**
 * Export all metrics in Prometheus text format.
 */
export function getPrometheusMetrics(): string {
  const sections = [
    `# Server uptime`,
    `process_start_time_seconds ${Math.floor(processStartTime / 1000)}`,
    `active_requests ${activeRequests}`,
    '',
    httpRequestsTotal.toPrometheus(),
    '',
    httpRequestDuration.toPrometheus(),
    '',
    httpErrorsTotal.toPrometheus(),
    '',
    logEntriesTotal.toPrometheus(),
  ];

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