import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { buildOtelSdk } from '../builder.js';
// Prevent dotenv from loading any .env file during tests
vi.mock('dotenv', () => ({ config: vi.fn() }));
// ---------- Mutable env stub ----------
// vi.hoisted ensures this object exists before vi.mock factories run (hoisting order).
const mockOtelConfig = vi.hoisted(() => ({
    enabled: false,
    serviceName: 'test-service',
    serviceNamespace: 'test-ns',
    deploymentEnv: 'test-env',
    exporterEndpoint: undefined,
    exporterHeaders: undefined,
    tracesSampler: 'always_on',
    tracesSamplerArg: undefined,
    startupStrict: false,
}));
vi.mock('../../environment.js', () => ({
    env: { otel: mockOtelConfig },
}));
// ---------- NodeSDK + tracing mock ----------
const MockNodeSDK = vi.hoisted(() => vi.fn().mockImplementation(function () {
    return { start: vi.fn(), shutdown: vi.fn() };
}));
const mockSamplers = vi.hoisted(() => ({
    AlwaysOnSampler: vi.fn().mockImplementation(function () {
        return { _type: 'AlwaysOnSampler' };
    }),
    AlwaysOffSampler: vi.fn().mockImplementation(function () {
        return { _type: 'AlwaysOffSampler' };
    }),
    ParentBasedSampler: vi.fn().mockImplementation(function (config) {
        return { _type: 'ParentBasedSampler', _config: config };
    }),
    TraceIdRatioBasedSampler: vi.fn().mockImplementation(function (ratio) {
        return { _type: 'TraceIdRatioBasedSampler', _ratio: ratio };
    }),
}));
const mockResource = vi.hoisted(() => ({ _type: 'resource' }));
const mockResourceFromAttributes = vi.hoisted(() => vi.fn().mockReturnValue(mockResource));
vi.mock('@opentelemetry/sdk-node', () => ({
    NodeSDK: MockNodeSDK,
    tracing: mockSamplers,
    resources: {
        resourceFromAttributes: mockResourceFromAttributes,
    },
}));
// ---------- OTLPTraceExporter mock ----------
const MockOTLPTraceExporter = vi.hoisted(() => vi.fn().mockImplementation(function (config) {
    return { _exporterConfig: config };
}));
vi.mock('@opentelemetry/exporter-trace-otlp-http', () => ({
    OTLPTraceExporter: MockOTLPTraceExporter,
}));
// ---------- auto-instrumentations mock ----------
const mockInstrumentationBundle = vi.hoisted(() => ({}));
vi.mock('@opentelemetry/auto-instrumentations-node', () => ({
    getNodeAutoInstrumentations: vi.fn().mockReturnValue(mockInstrumentationBundle),
}));
// -------------------------------------------------------
// Tests
// -------------------------------------------------------
describe('buildOtelSdk', () => {
    beforeEach(() => {
        vi.clearAllMocks();
        // Reset to a known disabled baseline before each test
        mockOtelConfig.enabled = false;
        mockOtelConfig.serviceName = 'test-service';
        mockOtelConfig.serviceNamespace = 'test-ns';
        mockOtelConfig.deploymentEnv = 'test-env';
        mockOtelConfig.exporterEndpoint = undefined;
        mockOtelConfig.exporterHeaders = undefined;
        mockOtelConfig.tracesSampler = 'always_on';
        mockOtelConfig.tracesSamplerArg = undefined;
        // Restore mock return values cleared by clearAllMocks
        mockResourceFromAttributes.mockReturnValue(mockResource);
    });
    // ---- disabled path ----
    describe('when OTEL is disabled', () => {
        it('returns null', () => {
            mockOtelConfig.enabled = false;
            expect(buildOtelSdk()).toBeNull();
        });
        it('does not construct NodeSDK', () => {
            mockOtelConfig.enabled = false;
            buildOtelSdk();
            expect(MockNodeSDK).not.toHaveBeenCalled();
        });
        it('does not construct OTLPTraceExporter', () => {
            mockOtelConfig.enabled = false;
            buildOtelSdk();
            expect(MockOTLPTraceExporter).not.toHaveBeenCalled();
        });
    });
    // ---- enabled path ----
    describe('when OTEL is enabled', () => {
        beforeEach(() => {
            mockOtelConfig.enabled = true;
            mockOtelConfig.exporterEndpoint = 'http://localhost:4318';
        });
        it('returns a NodeSDK instance', () => {
            const result = buildOtelSdk();
            expect(result).not.toBeNull();
            expect(MockNodeSDK).toHaveBeenCalledOnce();
        });
        it('builds resource with correct service attributes', () => {
            buildOtelSdk();
            expect(mockResourceFromAttributes).toHaveBeenCalledWith(expect.objectContaining({
                'service.name': 'test-service',
                'service.namespace': 'test-ns',
                'deployment.environment.name': 'test-env',
            }));
        });
        it('passes the built resource to NodeSDK', () => {
            buildOtelSdk();
            const [sdkConfig] = MockNodeSDK.mock.calls[0];
            expect(sdkConfig.resource).toBe(mockResource);
        });
        it('configures OTLPTraceExporter with the endpoint', () => {
            buildOtelSdk();
            expect(MockOTLPTraceExporter).toHaveBeenCalledWith(expect.objectContaining({ url: 'http://localhost:4318' }));
        });
        it('passes the OTLPTraceExporter instance to NodeSDK', () => {
            buildOtelSdk();
            const [sdkConfig] = MockNodeSDK.mock.calls[0];
            expect(sdkConfig.traceExporter).toBeDefined();
        });
        it('includes auto-instrumentations in NodeSDK config', () => {
            buildOtelSdk();
            const [sdkConfig] = MockNodeSDK.mock.calls[0];
            expect(sdkConfig.instrumentations).toContain(mockInstrumentationBundle);
        });
        it('configures fs/http/graphql auto-instrumentations', () => {
            buildOtelSdk();
            expect(getNodeAutoInstrumentations).toHaveBeenCalledWith(expect.objectContaining({
                '@opentelemetry/instrumentation-fs': expect.objectContaining({
                    enabled: false,
                }),
                '@opentelemetry/instrumentation-http': expect.objectContaining({
                    ignoreIncomingRequestHook: expect.any(Function),
                }),
                '@opentelemetry/instrumentation-graphql': expect.objectContaining({
                    enabled: true,
                }),
            }));
        });
        // ---- sampler selection ----
        describe('sampler configuration', () => {
            it('uses AlwaysOnSampler for always_on', () => {
                mockOtelConfig.tracesSampler = 'always_on';
                buildOtelSdk();
                expect(mockSamplers.AlwaysOnSampler).toHaveBeenCalledOnce();
                expect(mockSamplers.AlwaysOffSampler).not.toHaveBeenCalled();
                expect(mockSamplers.ParentBasedSampler).not.toHaveBeenCalled();
            });
            it('uses AlwaysOffSampler for always_off', () => {
                mockOtelConfig.tracesSampler = 'always_off';
                buildOtelSdk();
                expect(mockSamplers.AlwaysOffSampler).toHaveBeenCalledOnce();
                expect(mockSamplers.AlwaysOnSampler).not.toHaveBeenCalled();
            });
            it('uses ParentBasedSampler wrapping TraceIdRatioBasedSampler for parentbased_traceidratio', () => {
                mockOtelConfig.tracesSampler = 'parentbased_traceidratio';
                mockOtelConfig.tracesSamplerArg = 0.5;
                buildOtelSdk();
                expect(mockSamplers.TraceIdRatioBasedSampler).toHaveBeenCalledWith(0.5);
                expect(mockSamplers.ParentBasedSampler).toHaveBeenCalledOnce();
            });
            it('defaults ratio to 1.0 when parentbased_traceidratio arg is absent', () => {
                mockOtelConfig.tracesSampler = 'parentbased_traceidratio';
                mockOtelConfig.tracesSamplerArg = undefined;
                buildOtelSdk();
                expect(mockSamplers.TraceIdRatioBasedSampler).toHaveBeenCalledWith(1.0);
            });
            it('passes the sampler to NodeSDK', () => {
                mockOtelConfig.tracesSampler = 'always_on';
                buildOtelSdk();
                const [sdkConfig] = MockNodeSDK.mock.calls[0];
                expect(sdkConfig.sampler).toBeDefined();
            });
        });
        // ---- header parsing ----
        describe('headers configuration', () => {
            it('parses key=value pairs and passes them to OTLPTraceExporter', () => {
                mockOtelConfig.exporterHeaders = 'Authorization=Bearer token,X-Custom=value';
                buildOtelSdk();
                expect(MockOTLPTraceExporter).toHaveBeenCalledWith(expect.objectContaining({
                    headers: {
                        Authorization: 'Bearer token',
                        'X-Custom': 'value',
                    },
                }));
            });
            it('passes undefined headers when exporterHeaders is not configured', () => {
                mockOtelConfig.exporterHeaders = undefined;
                buildOtelSdk();
                expect(MockOTLPTraceExporter).toHaveBeenCalledWith(expect.objectContaining({ headers: undefined }));
            });
        });
    });
});
//# sourceMappingURL=builder.test.js.map