import { ApiInternalServerErrorException } from "@components/api-exceptions"
import Elysia from "elysia"
import client, { type Metric } from "prom-client"

/**
 * Builder class for creating and configuring Prometheus metrics
 *
 * @example
 * const metricsBuilder = new MetricsBuilder()
 *   .withPrefix('myapp_')
 *   .withoutDefaultMetrics(false)
 *   .build()
 */
export class MetricsBuilder {
  private register: client.Registry
  private prefix = "app_"
  private defaultMetrics = true

  constructor() {
    this.register = new client.Registry()
  }

  /**
   * Sets the prefix for all metrics
   * @param prefix - The prefix to add to all metric names
   * @returns The builder instance for method chaining
   */
  withPrefix(prefix: string) {
    this.prefix = prefix
    return this
  }

  /**
   * Configures whether to collect default metrics
   * @param defaultMetrics - If false, disables default metrics collection
   * @returns The builder instance for method chaining
   */
  withDefaultMetrics(newVal: boolean) {
    this.defaultMetrics = newVal
    return this
  }

  /**
   * Builds and returns the metrics registry with the configured settings
   * @returns An object containing registry methods and properties
   */
  build() {
    if (this.defaultMetrics) {
      client.collectDefaultMetrics({
        register: this.register,
        prefix: this.prefix,
      })
    }

    return {
      contentType: this.register.contentType,
      registerMetrics: this.register.registerMetric.bind(this.register),
      metrics: this.register.metrics.bind(this.register),
      getMetric: this.register.getSingleMetric.bind(this.register),
    }
  }
}

/**
 * Factory function to create an Elysia plugin with metrics registry
 * @param defaultMetrics - Whether to collect default metrics
 * @param prefix - Prefix for metric names
 * @returns Elysia plugin with configured metrics registry
 */
export const metricsFactory = (defaultMetrics = true, prefix = "app_") =>
  new Elysia().decorate(
    "registry",
    new MetricsBuilder().withPrefix(prefix).withDefaultMetrics(defaultMetrics).build(),
  )

/**
 * Factory function to create an Elysia plugin with a pre-configured metrics registry
 * @param metricsBuilder - A built metrics registry instance from MetricsBuilder
 * @returns Elysia plugin decorated with the provided metrics registry
 */
export const globalMetricsFactory = (metricsBuilder: ReturnType<typeof MetricsBuilder.prototype.build>) =>
  new Elysia().decorate("registry", metricsBuilder)

/**
 * Creates an Elysia plugin that registers and retrieves a specific metric
 * @param metric - The Prometheus metric to register
 * @returns Elysia plugin that provides access to the metric
 * @throws Error if metrics registry is not found
 */
export const getMetric = <T extends Metric>(metric: T) =>
  new Elysia().derive({ as: "scoped" }, async (context) => {
    // biome-ignore lint/suspicious/noExplicitAny: Required for context type
    const registry = (context as any).registry as ReturnType<typeof MetricsBuilder.prototype.build>
    if (registry) {
      const metricValue = await metric.get()

      const registeredMetric = registry.getMetric(metricValue.name)

      if (!registeredMetric) {
        registry.registerMetrics(metric)
      }

      return {
        [metricValue.name]: metric,
      }
    }

    throw new ApiInternalServerErrorException("Metric registry not found")
  })

/**
 * Creates an Elysia plugin that provides access to the metrics registry
 * @returns Elysia plugin with registry access
 * @throws Error if metrics registry is not found
 */
export const getRegistry = () =>
  new Elysia().derive({ as: "scoped" }, async (context) => {
    // biome-ignore lint/suspicious/noExplicitAny: Required for context type
    const registry = (context as any).registry as ReturnType<typeof MetricsBuilder.prototype.build>
    if (registry) {
      return {
        registry,
      }
    }

    throw new ApiInternalServerErrorException("Metrics registry not found")
  })
