package log

import (
	"context"
	"crypto/rand"
	"fmt"
	"os"
	"path/filepath"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"

	"github.com/pingcap/log"
	"go.uber.org/zap"
)

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

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, attributeModule, moduleName)
		}
		if operationStr != "" {
			ctxWithSpan = context.WithValue(ctxWithSpan, attributeOperation, 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, attributeModule, moduleName)
	}
	if operationStr != "" {
		ctxWithSpan = context.WithValue(ctxWithSpan, attributeOperation, operationStr)
	}
	return ctxWithSpan

}

func StartTraceWithTraceId(traceId, moduleName string, operationName ...string) (context.Context, error) {

	tid, err := trace.TraceIDFromHex(traceId)
	if err != nil {
		PlainError("Invalid trace ID:", zap.Error(err))
		return nil, err
	}

	spanCtx := trace.NewSpanContext(trace.SpanContextConfig{
		TraceID:    tid,
		SpanID:     generateSpanID(),
		TraceFlags: trace.FlagsSampled,
		Remote:     true,
	})

	ctx := trace.ContextWithSpanContext(context.Background(), spanCtx)

	tracer := otel.Tracer(moduleName)
	operationStr := ""
	if len(operationName) > 0 {
		operationStr = operationName[0]
	}

	ctx, span := tracer.Start(ctx, operationStr)
	if !span.SpanContext().HasTraceID() {
		spanCtx = trace.NewSpanContext(trace.SpanContextConfig{
			TraceID:    tid,
			SpanID:     generateSpanID(),
			TraceFlags: trace.FlagsSampled,
			Remote:     true,
		})
		ctxWithSpan := trace.ContextWithSpanContext(context.Background(), spanCtx)
		if moduleName != "" {
			ctxWithSpan = context.WithValue(ctxWithSpan, attributeModule, moduleName)
		}
		if operationStr != "" {
			ctxWithSpan = context.WithValue(ctxWithSpan, attributeOperation, operationStr)
		}
		return ctxWithSpan, nil
	}

	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, attributeModule, moduleName)
	}
	if operationStr != "" {
		ctxWithSpan = context.WithValue(ctxWithSpan, attributeOperation, operationStr)
	}
	return ctxWithSpan, nil
}

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 GeTraceId(ctx context.Context) string {
	return trace.SpanContextFromContext(ctx).TraceID().String()
}

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

// InitLog initializes the log with file output configuration
func InitLog(logDir string) error {
	if !config.IsEC2() {
		fmt.Printf("logDir: %s\n", logDir)
		return nil
	}
	if logDir == "" {
		// If no log directory specified, use default console output
		return nil
	}

	// Ensure log directory exists
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	// Configure log with file output
	logConfig := &log.Config{
		Level:  "info",
		Format: "text", // or "json" for structured logs
		File: log.FileLogConfig{
			Filename:   filepath.Join(logDir, "slow_query.log"),
			MaxSize:    100, // MB
			MaxDays:    7,   // days
			MaxBackups: 3,
		},
	}

	// Initialize logger with the configuration
	logger, props, err := log.InitLogger(logConfig)
	if err != nil {
		return fmt.Errorf("failed to initialize logger: %w", err)
	}

	// Replace global logger
	log.ReplaceGlobals(logger, props)
	log.Info("init log success")
	return nil
}
