export const OTEL_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'https://otel-logging.flunti.io'
export const OTEL_SERVICE_NAME = process.env.OTEL_SERVICE_NAME ?? 'app'
export const OTEL_INGEST_TOKEN = ''
const OTEL_SHIP_TIMEOUT_MS = 800
const OTEL_BODY_MAX_BYTES = 8192

const SENSITIVE_HEADERS = ['authorization', 'cookie', 'set-cookie', 'x-access-token', 'x-refresh-token', 'proxy-authorization']
const SENSITIVE_BODY_KEYS = ['password', 'token', 'access_token', 'refresh_token', 'authorization', 'secret', 'apikey', 'api_key']
const MASK = '***MASKED***'

export const maskHeaders = (headers: Headers) => {
    const record: Record<string, string> = {}
    headers.forEach((value, key) => {
        const lower = key.toLowerCase()
        record[key] = SENSITIVE_HEADERS.includes(lower) || lower.includes('token') ? MASK : value
    })
    return record
}

export const maskBody = (body: string) => {
    const masked = SENSITIVE_BODY_KEYS.reduce((acc, sensitiveKey) => {
        const pattern = new RegExp(`("${sensitiveKey}"\\s*:\\s*)"[^"]*"`, 'gi')
        return acc.replace(pattern, `$1"${MASK}"`)
    }, body)
    return masked.length > OTEL_BODY_MAX_BYTES ? `${masked.slice(0, OTEL_BODY_MAX_BYTES)}…[truncated]` : masked
}

type AttrPrimitive = string | number | boolean
type LogInput = {
    message: string
    severityText?: string
    severityNumber?: number
    attributes?: Record<string, AttrPrimitive>
    traceId?: string
}
type SpanInput = {
    name: string
    kind?: number
    startMs: number
    endMs: number
    status: 'ok' | 'error'
    attributes?: Record<string, AttrPrimitive>
    traceId?: string
    spanId?: string
}

const toHex = (bytes: number) => {
    const buffer = new Uint8Array(bytes)
    crypto.getRandomValues(buffer)
    return Array.from(buffer, (value) => value.toString(16).padStart(2, '0')).join('')
}

const toNano = (ms: number) => `${Math.floor(ms)}000000`

const toAttributes = (attributes?: Record<string, AttrPrimitive>) =>
    Object.entries(attributes ?? {}).map(([key, value]) => ({
        key,
        value:
            typeof value === 'number'
                ? Number.isInteger(value)
                    ? { intValue: String(value) }
                    : { doubleValue: value }
                : typeof value === 'boolean'
                  ? { boolValue: value }
                  : { stringValue: value },
    }))

const post = async (path: string, payload: unknown) => {
    if (!OTEL_ENDPOINT || !OTEL_INGEST_TOKEN) return
    try {
        await fetch(`${OTEL_ENDPOINT}${path}`, {
            method: 'POST',
            headers: OTEL_INGEST_TOKEN
                ? { 'content-type': 'application/json', 'x-ingest-token': OTEL_INGEST_TOKEN }
                : { 'content-type': 'application/json' },
            body: JSON.stringify(payload),
            keepalive: true,
            signal: AbortSignal.timeout(OTEL_SHIP_TIMEOUT_MS),
        })
    } catch {
        return
    }
}

export const shipLogs = (records: LogInput[]) =>
    post('/v1/logs', {
        resourceLogs: [
            {
                resource: { attributes: toAttributes({ 'service.name': OTEL_SERVICE_NAME }) },
                scopeLogs: [
                    {
                        scope: { name: 'proxy' },
                        logRecords: records.map((record) => ({
                            timeUnixNano: toNano(Date.now()),
                            severityNumber: record.severityNumber ?? 9,
                            severityText: record.severityText ?? 'INFO',
                            body: { stringValue: record.message },
                            traceId: record.traceId,
                            attributes: toAttributes({ 'log.source': 'proxy', ...record.attributes }),
                        })),
                    },
                ],
            },
        ],
    })

export const shipSpans = (spans: SpanInput[]) =>
    post('/v1/traces', {
        resourceSpans: [
            {
                resource: { attributes: toAttributes({ 'service.name': OTEL_SERVICE_NAME }) },
                scopeSpans: [
                    {
                        scope: { name: 'proxy' },
                        spans: spans.map((span) => ({
                            traceId: span.traceId ?? toHex(16),
                            spanId: span.spanId ?? toHex(8),
                            name: span.name,
                            kind: span.kind ?? 2,
                            startTimeUnixNano: toNano(span.startMs),
                            endTimeUnixNano: toNano(span.endMs),
                            attributes: toAttributes(span.attributes),
                            status: { code: span.status === 'error' ? 2 : 0 },
                        })),
                    },
                ],
            },
        ],
    })

export const createProxyLogger = () => {
    const buffer: LogInput[] = []
    const traceId = toHex(16)
    return {
        traceId,
        log: (message: string, attributes?: Record<string, AttrPrimitive>) => {
            console.log(message)
            buffer.push({ message, severityText: 'INFO', severityNumber: 9, attributes, traceId })
        },
        error: (message: string, attributes?: Record<string, AttrPrimitive>) => {
            console.error(message)
            buffer.push({ message, severityText: 'ERROR', severityNumber: 17, attributes, traceId })
        },
        flush: async () => {
            if (buffer.length) await shipLogs(buffer.splice(0))
        },
    }
}

export const reportProxySpan = (input: SpanInput) => shipSpans([input])

const decodePathname = (pathname: string) => {
    try {
        return decodeURIComponent(pathname)
    } catch {
        return pathname
    }
}

export const reportProxyTraffic = (req: Request, startMs: number, response: Response, statusOverride?: number) => {
    const pathname = decodePathname(new URL(req.url).pathname)
    const status = statusOverride ?? response.status
    void reportProxySpan({
        name: `proxy ${req.method} ${pathname}`,
        startMs,
        endMs: Date.now(),
        status: status >= 400 ? 'error' : 'ok',
        attributes: {
            'http.request.method': req.method,
            'http.route': pathname,
            'http.response.status_code': status,
        },
    })
}

export const otelFetch = async (name: string, input: string, init?: RequestInit) => {
    const startMs = Date.now()
    try {
        const response = await fetch(input, init)
        void reportProxySpan({
            name: `oiia ${name}`,
            kind: 3,
            startMs,
            endMs: Date.now(),
            status: response.status >= 400 ? 'error' : 'ok',
            attributes: { 'http.request.method': init?.method ?? 'GET', 'http.url': input, 'http.response.status_code': response.status },
        })
        return response
    } catch (error) {
        const message = error instanceof Error ? error.message : String(error)
        void reportProxySpan({
            name: `oiia ${name}`,
            kind: 3,
            startMs,
            endMs: Date.now(),
            status: 'error',
            attributes: { 'http.request.method': init?.method ?? 'GET', 'http.url': input },
        })
        void shipLogs([
            {
                message: `oiia ${name} error: ${message}`,
                severityText: 'ERROR',
                severityNumber: 17,
                attributes: { 'log.source': 'proxy-fetch', 'oiia.name': name, 'http.url': input },
            },
        ])
        throw error
    }
}

export const reportRevalidate = (tags: string[], layout?: string) =>
    void shipLogs([
        {
            message: `revalidate ${tags.join(', ')}${layout ? ` + layout ${layout}` : ''}`,
            attributes: {
                'log.source': 'revalidate',
                'event.name': 'cache.revalidate',
                'cache.tags': tags.join(','),
                'cache.layout': layout ?? '',
            },
        },
    ])

export const newTraceId = () => toHex(16)
export const newSpanId = () => toHex(8)
export const toTraceparent = (traceId: string, spanId: string) => `00-${traceId}-${spanId}-01`
