import { record as elysiaRecord } from "@elysiajs/opentelemetry";
import { getLogger } from "@logtape/logtape";
import { metrics as metricapi } from "@opentelemetry/api";
import { logs as logapi } from "@opentelemetry/api-logs";
import { SDK_INFO } from "@opentelemetry/core";
import { OTLPLogExporter as OLTPLogExporterGRPC } from "@opentelemetry/exporter-logs-otlp-grpc";
import { OTLPLogExporter as OLTPLogExporterHTTPjson } from "@opentelemetry/exporter-logs-otlp-http";
import { OTLPLogExporter as OLTPLogExporterHTTPprotobuf } from "@opentelemetry/exporter-logs-otlp-proto";
import { OTLPMetricExporter as OLTPMetricsExporterGRPC } from "@opentelemetry/exporter-metrics-otlp-grpc";
// does not currently have a shared 'base'.  Need to use from http package
import type { OTLPMetricExporterBase } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPMetricExporter as OLTPMetricsExporterHTTPjson } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPMetricExporter as OLTPMetricsExporterHTTPprotobuf } from "@opentelemetry/exporter-metrics-otlp-proto";
import { OTLPTraceExporter as OLTPTraceExporterGRPC } from "@opentelemetry/exporter-trace-otlp-grpc";
import { OTLPTraceExporter as OLTPTraceExporterHTTPjson } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPTraceExporter as OLTPTraceExporterHTTPprotobuf } from "@opentelemetry/exporter-trace-otlp-proto";
import { resourceFromAttributes } from "@opentelemetry/resources";
import type { LogRecordExporter } from "@opentelemetry/sdk-logs";
import {
	BatchLogRecordProcessor,
	LoggerProvider,
	type LogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import {
	MeterProvider,
	PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import type {
	ReadableSpan,
	SpanExporter,
	SpanProcessor,
} from "@opentelemetry/sdk-trace-base";
import {
	BatchSpanProcessor,
	NodeTracerProvider,
} from "@opentelemetry/sdk-trace-node";
import {
	ATTR_SERVICE_NAME,
	ATTR_TELEMETRY_SDK_LANGUAGE,
	ATTR_TELEMETRY_SDK_NAME,
	ATTR_TELEMETRY_SDK_VERSION,
} from "@opentelemetry/semantic-conventions";

const resource = resourceFromAttributes({
	[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "kyoo.api",
	[ATTR_TELEMETRY_SDK_LANGUAGE]: SDK_INFO[ATTR_TELEMETRY_SDK_LANGUAGE],
	[ATTR_TELEMETRY_SDK_NAME]: SDK_INFO[ATTR_TELEMETRY_SDK_NAME],
	[ATTR_TELEMETRY_SDK_VERSION]: SDK_INFO[ATTR_TELEMETRY_SDK_VERSION],
});

const logger = getLogger();

function stripPresignSpanProcessor(): SpanProcessor {
	const stripQuery = (query: string): string => {
		const params = new URLSearchParams(query);
		if (!params.has("x-presign")) return query;
		params.delete("x-presign");
		return params.toString();
	};
	const stripUrl = (url: string): string => {
		try {
			const u = new URL(url);
			if (!u.searchParams.has("x-presign")) return url;
			u.searchParams.delete("x-presign");
			return u.toString();
		} catch {
			return url;
		}
	};
	return {
		onStart() {},
		onEnd(span: ReadableSpan) {
			const attrs = span.attributes as Record<string, unknown>;
			if (typeof attrs["url.full"] === "string")
				attrs["url.full"] = stripUrl(attrs["url.full"]);
			if (typeof attrs["url.query"] === "string")
				attrs["url.query"] = stripQuery(attrs["url.query"]);
		},
		forceFlush: async () => {},
		shutdown: async () => {},
	};
}

// all logs in kyoo are in uppercase by default, also make it uppercase here.
function upperCaseSeverityTextProcessor(): LogRecordProcessor {
	return {
		onEmit(logRecord) {
			const record = logRecord as { severityText?: string };
			if (typeof record.severityText === "string") {
				record.severityText = record.severityText.toUpperCase();
			}
		},
		forceFlush: async () => {},
		shutdown: async () => {},
	};
}

export function setupOtel() {
	logger.info("Configuring OTEL");
	const protocol = (
		process.env.OTEL_EXPORTER_OTLP_PROTOCOL || ""
	).toLowerCase();

	let le: LogRecordExporter | null;
	let me: OTLPMetricExporterBase | null;
	let te: SpanExporter | null;

	switch (true) {
		case !process.env.OTEL_EXPORTER_OTLP_ENDPOINT:
			logger.info("Using OLTP type: {type}", {
				type: "noop",
			});
			le = null;
			me = null;
			te = null;
			break;
		case protocol === "grpc":
			logger.info("Using OLTP type: {type}", {
				type: "grpc",
			});
			le = new OLTPLogExporterGRPC();
			me = new OLTPMetricsExporterGRPC();
			te = new OLTPTraceExporterGRPC();
			break;
		case protocol === "http/json":
		case protocol === "http_json":
		case protocol === "httpjson":
			logger.info("Using OLTP type: {type}", {
				type: "http/json",
			});
			le = new OLTPLogExporterHTTPjson();
			me = new OLTPMetricsExporterHTTPjson();
			te = new OLTPTraceExporterHTTPjson();
			break;
		default:
			logger.info("Using OLTP type: {type}", {
				type: "http/protobuf",
			});
			le = new OLTPLogExporterHTTPprotobuf();
			me = new OLTPMetricsExporterHTTPprotobuf();
			te = new OLTPTraceExporterHTTPprotobuf();
			break;
	}

	let lp = new LoggerProvider({ resource });
	let mp = new MeterProvider({ resource });
	let tp = new NodeTracerProvider({ resource });

	if (le) {
		lp = new LoggerProvider({
			resource,
			processors: [
				upperCaseSeverityTextProcessor(),
				new BatchLogRecordProcessor(le),
			],
		});
	}

	if (me) {
		mp = new MeterProvider({
			resource,
			readers: [
				new PeriodicExportingMetricReader({
					exporter: me,
				}),
			],
		});
	}

	if (te) {
		tp = new NodeTracerProvider({
			resource,
			spanProcessors: [stripPresignSpanProcessor(), new BatchSpanProcessor(te)],
		});
	}

	logapi.setGlobalLoggerProvider(lp);
	metricapi.setGlobalMeterProvider(mp);
	tp.register();
}

export function record<T extends (...args: any) => any>(
	spanName: string,
	fn: T,
): T {
	const wrapped = (...args: Parameters<T>) =>
		elysiaRecord(spanName, () => fn(...args));
	return wrapped as T;
}
