package tracing

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"github.com/google/uuid"
)

const (
	// W3C Trace Context headers
	TraceParentHeader = "traceparent"
	TraceStateHeader  = "tracestate"

	// Legacy headers (for backward compatibility)
	LegacyTraceIDHeader = "X-Trace-ID"
	LegacySpanIDHeader  = "X-Span-ID"
)

// TraceContext représente un contexte de tracing distribué (BE-SVC-018)
type TraceContext struct {
	TraceID    string
	SpanID     string
	ParentSpan string // ID du span parent (pour hiérarchie)
	Sampled    bool   // Indique si le trace doit être échantillonné
}

// NewTraceContext crée un nouveau contexte de tracing
func NewTraceContext() *TraceContext {
	return &TraceContext{
		TraceID: uuid.New().String(),
		SpanID:  uuid.New().String(),
		Sampled: true, // Par défaut, on échantillonne tous les traces
	}
}

// NewChildSpan crée un nouveau span enfant à partir d'un contexte parent
func (tc *TraceContext) NewChildSpan() *TraceContext {
	return &TraceContext{
		TraceID:    tc.TraceID, // Même trace ID
		SpanID:     uuid.New().String(),
		ParentSpan: tc.SpanID, // Le span actuel devient le parent
		Sampled:    tc.Sampled,
	}
}

// ToW3CTraceParent convertit le contexte en format W3C Trace Context (traceparent)
// Format: version-trace_id-parent_id-trace_flags
// Exemple: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
func (tc *TraceContext) ToW3CTraceParent() string {
	version := "00" // Version 00 (actuelle)
	traceID := formatTraceID(tc.TraceID)
	parentID := formatSpanID(tc.ParentSpan)
	if parentID == "" {
		parentID = "0000000000000000" // Pas de parent
	}
	flags := "01" // Sampled flag
	if !tc.Sampled {
		flags = "00"
	}
	return fmt.Sprintf("%s-%s-%s-%s", version, traceID, parentID, flags)
}

// FromW3CTraceParent parse un header traceparent W3C
func FromW3CTraceParent(traceParent string) (*TraceContext, error) {
	parts := strings.Split(traceParent, "-")
	if len(parts) != 4 {
		return nil, fmt.Errorf("invalid traceparent format")
	}

	version := parts[0]
	if version != "00" {
		return nil, fmt.Errorf("unsupported traceparent version: %s", version)
	}

	traceID := parts[1]
	parentID := parts[2]
	flags := parts[3]

	// Parser les flags (dernier caractère = sampled flag)
	sampled := flags[len(flags)-1] == '1'

	tc := &TraceContext{
		TraceID:    traceID,
		SpanID:     uuid.New().String(), // Nouveau span ID pour ce service
		ParentSpan: parentID,
		Sampled:    sampled,
	}

	return tc, nil
}

// formatTraceID formate un trace ID pour W3C (32 caractères hex)
func formatTraceID(id string) string {
	// Supprimer les tirets si c'est un UUID
	id = strings.ReplaceAll(id, "-", "")
	// S'assurer que c'est exactement 32 caractères hex
	if len(id) > 32 {
		id = id[:32]
	} else if len(id) < 32 {
		// Padding avec des zéros
		id = id + strings.Repeat("0", 32-len(id))
	}
	return id
}

// formatSpanID formate un span ID pour W3C (16 caractères hex)
func formatSpanID(id string) string {
	if id == "" {
		return ""
	}
	// Supprimer les tirets si c'est un UUID
	id = strings.ReplaceAll(id, "-", "")
	// S'assurer que c'est exactement 16 caractères hex
	if len(id) > 16 {
		id = id[:16]
	} else if len(id) < 16 {
		// Padding avec des zéros
		id = id + strings.Repeat("0", 16-len(id))
	}
	return id
}

// InjectTraceContext injecte le contexte de tracing dans les headers HTTP
func InjectTraceContext(req *http.Request, tc *TraceContext) {
	if tc == nil {
		return
	}

	// Injecter le format W3C Trace Context (standard)
	req.Header.Set(TraceParentHeader, tc.ToW3CTraceParent())

	// Injecter aussi les headers legacy pour compatibilité
	req.Header.Set(LegacyTraceIDHeader, tc.TraceID)
	req.Header.Set(LegacySpanIDHeader, tc.SpanID)
}

// ExtractTraceContext extrait le contexte de tracing depuis les headers HTTP
func ExtractTraceContext(req *http.Request) *TraceContext {
	// Essayer d'abord le format W3C
	if traceParent := req.Header.Get(TraceParentHeader); traceParent != "" {
		if tc, err := FromW3CTraceParent(traceParent); err == nil {
			return tc
		}
	}

	// Fallback sur les headers legacy
	traceID := req.Header.Get(LegacyTraceIDHeader)
	spanID := req.Header.Get(LegacySpanIDHeader)

	if traceID == "" && spanID == "" {
		// Aucun contexte, créer un nouveau
		return NewTraceContext()
	}

	// Créer un contexte à partir des headers legacy
	if traceID == "" {
		traceID = uuid.New().String()
	}
	if spanID == "" {
		spanID = uuid.New().String()
	}

	return &TraceContext{
		TraceID: traceID,
		SpanID:  spanID,
		Sampled: true,
	}
}

// ContextKey est le type pour la clé de contexte
type contextKey string

const traceContextKey contextKey = "trace_context"

// WithTraceContext ajoute un contexte de tracing au contexte Go
func WithTraceContext(ctx context.Context, tc *TraceContext) context.Context {
	return context.WithValue(ctx, traceContextKey, tc)
}

// FromContext extrait le contexte de tracing depuis le contexte Go
func FromContext(ctx context.Context) *TraceContext {
	if tc, ok := ctx.Value(traceContextKey).(*TraceContext); ok {
		return tc
	}
	return nil
}

// GetOrCreateTraceContext obtient le contexte de tracing depuis le contexte Go, ou en crée un nouveau
func GetOrCreateTraceContext(ctx context.Context) *TraceContext {
	if tc := FromContext(ctx); tc != nil {
		return tc
	}
	return NewTraceContext()
}
