package log

import (
	"context"
	"crypto/rand"
	"net/http"
	"strings"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"go.uber.org/zap"
)

var sensitiveHeaderKeywords = []string{"token", "csrf", "auth", "secret", "key", "password", "cookie"}

const (
	attributeModule    = "module"
	attributeOperation = "operation"
)

type contextKey string

const (
	contextKeyModule    contextKey = "module"
	contextKeyOperation contextKey = "operation"
)

func StartTrace(moduleName string, operationName ...string) context.Context {
	newCtx := context.Background()
	operationStr := ""
	tracer := otel.Tracer(moduleName)
	if len(operationName) != 0 {
		operationStr = operationName[0]
	}
	ctx, span := tracer.Start(newCtx, operationStr)
	if span.SpanContext().HasTraceID() {
		if moduleName != "" {
			span.SetAttributes(attribute.String(attributeModule, moduleName))
		}
		if operationStr != "" {
			span.SetAttributes(attribute.String(attributeOperation, operationStr))
		}
		ctxWithSpan := trace.ContextWithSpan(ctx, span)
		if moduleName != "" {
			ctxWithSpan = context.WithValue(ctxWithSpan, contextKeyModule, moduleName)
		}
		if operationStr != "" {
			ctxWithSpan = context.WithValue(ctxWithSpan, contextKeyOperation, operationStr)
		}
		return ctxWithSpan
	}

	spanCtx := trace.NewSpanContext(trace.SpanContextConfig{
		TraceID:    generateTraceID(),
		SpanID:     generateSpanID(),
		TraceFlags: trace.FlagsSampled,
	})
	ctxWithSpan := trace.ContextWithSpanContext(newCtx, spanCtx)
	if moduleName != "" {
		ctxWithSpan = context.WithValue(ctxWithSpan, contextKeyModule, moduleName)
	}
	if operationStr != "" {
		ctxWithSpan = context.WithValue(ctxWithSpan, contextKeyOperation, operationStr)
	}
	return ctxWithSpan
}

func EndTrace(ctx context.Context) {
	trace.SpanFromContext(ctx).End()
}

func GeTraceId(ctx context.Context) string {
	return trace.SpanContextFromContext(ctx).TraceID().String()
}

func generateSpanID() trace.SpanID {
	var spanID trace.SpanID
	_, _ = rand.Read(spanID[:])
	return spanID
}

func generateTraceID() trace.TraceID {
	var traceID trace.TraceID
	_, _ = rand.Read(traceID[:])
	return traceID
}

func RedactedHeaderFields(header http.Header) []zap.Field {
	fields := make([]zap.Field, 0, len(header))
	for k, v := range header {
		if isSensitiveHeader(k) {
			fields = append(fields, zap.String(k, "[REDACTED]"))
		} else {
			fields = append(fields, zap.String(k, strings.Join(v, ",")))
		}
	}
	return fields
}

func isSensitiveHeader(name string) bool {
	lowerName := strings.ToLower(name)
	for _, kw := range sensitiveHeaderKeywords {
		if strings.Contains(lowerName, kw) {
			return true
		}
	}
	return false
}
