import { context, metrics, SpanStatusCode, trace } from "@opentelemetry/api";

type TelemetryContext = {
    req: {
        method: string;
        path: string;
    };
    res: {
        status: number;
    };
};

export function createHttpTelemetryMiddleware(serviceName: string) {
    const tracer = trace.getTracer(`${serviceName}.http`);
    const meter = metrics.getMeter(`${serviceName}.http`);

    const requestCount = meter.createCounter(`${serviceName}.http.server.requests`, {
        description: `Total number of ${serviceName} HTTP requests`,
    });

    const requestDuration = meter.createHistogram(
        `${serviceName}.http.server.duration`,
        {
            description: `${serviceName} HTTP request duration`,
            unit: "ms",
        },
    );

    const activeRequests = meter.createUpDownCounter(
        `${serviceName}.http.server.active_requests`,
        {
            description: `Number of in-flight ${serviceName} HTTP requests`,
        },
    );

    return async (c: TelemetryContext, next: () => Promise<unknown>) => {
        const startedAt = performance.now();
        const method = c.req.method;
        const route = c.req.path;

        const span = tracer.startSpan(`http ${method} ${route}`, {
            attributes: {
                "http.request.method": method,
                "url.path": route,
            },
        });

        activeRequests.add(1, {
            "http.request.method": method,
            "url.path": route,
        });

        try {
            await context.with(trace.setSpan(context.active(), span), next);

            span.setAttribute("http.response.status_code", c.res.status);
            span.setStatus({
                code:
                    c.res.status >= 500
                        ? SpanStatusCode.ERROR
                        : SpanStatusCode.OK,
            });
        } catch (error) {
            span.recordException(error as Error);
            span.setStatus({
                code: SpanStatusCode.ERROR,
                message: `Unhandled ${serviceName} request error`,
            });
            throw error;
        } finally {
            const durationMs = performance.now() - startedAt;
            const attrs = {
                "http.request.method": method,
                "http.response.status_code": c.res.status,
                "url.path": route,
            };

            requestCount.add(1, attrs);
            requestDuration.record(durationMs, attrs);
            activeRequests.add(-1, {
                "http.request.method": method,
                "url.path": route,
            });

            span.end();
        }
    };
}
