import { describe, test, expect, vi, beforeEach, assert } from "vitest";
import { getMetric, MetricsParams } from "../lib/metrics";

const mockMeter = {
  createGauge: vi.fn(),
  createHistogram: vi.fn(),
  createCounter: vi.fn(),
  createUpDownCounter: vi.fn(),
  createObservableCounter: vi.fn(),
  createObservableUpDownCounter: vi.fn(),
  createObservableGauge: vi.fn(),
};

vi.mock("@opentelemetry/api", async () => {
  const { createNoopMeter } = await import("@opentelemetry/api");

  return {
    metrics: {
      getMeter: vi.fn(() => mockMeter),
    },
    createNoopMeter: createNoopMeter,
  };
});

describe("MetricsFactoryMap", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  const validMetricParams: MetricsParams = {
    metricName: "test-metric",
    meterName: "test-meter",
    options: { description: "A test metric" },
  };

  test("should call createGauge when type is 'gauge'", () => {
    mockMeter.createGauge.mockReturnValue("mocked-gauge");

    const result = getMetric("gauge", validMetricParams);

    expect(result).toBe("mocked-gauge");
    expect(mockMeter.createGauge).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createHistogram when type is 'histogram'", () => {
    mockMeter.createHistogram.mockReturnValue("mocked-histogram");

    const result = getMetric("histogram", validMetricParams);

    expect(result).toBe("mocked-histogram");
    expect(mockMeter.createHistogram).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createCounter when type is 'counter'", () => {
    mockMeter.createCounter.mockReturnValue("mocked-counter");

    const result = getMetric("counter", validMetricParams);

    expect(result).toBe("mocked-counter");
    expect(mockMeter.createCounter).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createUpDownCounter when type is 'updowncounter'", () => {
    mockMeter.createUpDownCounter.mockReturnValue("mocked-updowncounter");

    const result = getMetric("updowncounter", validMetricParams);

    expect(result).toBe("mocked-updowncounter");
    expect(mockMeter.createUpDownCounter).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createObservableCounter when type is 'async-counter'", () => {
    mockMeter.createObservableCounter.mockReturnValue("mocked-async-counter");

    const result = getMetric("async-counter", validMetricParams);

    expect(result).toBe("mocked-async-counter");
    expect(mockMeter.createObservableCounter).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createObservableUpDownCounter when type is 'async-updowncounter'", () => {
    mockMeter.createObservableUpDownCounter.mockReturnValue(
      "mocked-async-updowncounter",
    );

    const result = getMetric("async-updowncounter", validMetricParams);

    expect(result).toBe("mocked-async-updowncounter");
    expect(mockMeter.createObservableUpDownCounter).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should call createObservableGauge when type is 'async-gauge'", () => {
    mockMeter.createObservableGauge.mockReturnValue("mocked-async-gauge");

    const result = getMetric("async-gauge", validMetricParams);

    expect(result).toBe("mocked-async-gauge");
    expect(mockMeter.createObservableGauge).toHaveBeenCalledWith(
      validMetricParams.metricName,
      validMetricParams.options,
    );
  });

  test("should throw an error for unsupported metric types", () => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const invalidMetricType = "invalid-type" as any;

    expect(() => getMetric(invalidMetricType, validMetricParams)).toThrow(
      `Unsupported metric type: ${invalidMetricType}`,
    );
  });

  test("should return noop metric fallback for null config", async () => {
    const nullMetricParams: MetricsParams = {
      metricName: null!,
      meterName: "",
    };

    const result = getMetric("async-gauge", nullMetricParams);

    assert.isNotNull(result);
    assert.equal(result.constructor.name, "NoopObservableGaugeMetric");
  });
});
