package services

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/google/uuid" // Added import for uuid

	"go.uber.org/zap"
)

type StreamService struct {
	baseURL        string
	client         *http.Client
	circuitBreaker *CircuitBreakerHTTPClient
	logger         *zap.Logger
}

func NewStreamService(baseURL string, logger *zap.Logger) *StreamService {
	if logger == nil {
		logger = zap.NewNop()
	}
	httpClient := &http.Client{Timeout: 10 * time.Second}
	return &StreamService{
		baseURL:        baseURL,
		client:         httpClient,
		circuitBreaker: NewCircuitBreakerHTTPClient(httpClient, "stream-service", logger),
		logger:         logger,
	}
}

type TranscodeRequest struct {
	TrackID  string `json:"track_id"`
	FilePath string `json:"file_path"`
}

func (s *StreamService) StartProcessing(ctx context.Context, trackID uuid.UUID, filePath string) error { // Changed trackID to uuid.UUID
	url := fmt.Sprintf("%s/internal/jobs/transcode", s.baseURL)
	reqBody := TranscodeRequest{
		TrackID:  trackID.String(), // Converted uuid.UUID to string
		FilePath: filePath,
	}

	jsonBody, err := json.Marshal(reqBody)
	if err != nil {
		return fmt.Errorf("failed to marshal request: %w", err)
	}

	// MOD-P1-RES-002: Ajouter retry avec backoff exponentiel (pattern similaire à WebhookService)
	maxRetries := 3
	backoff := time.Second

	for i := 0; i < maxRetries; i++ {
		// Vérifier si le contexte est annulé avant chaque tentative
		select {
		case <-ctx.Done():
			return fmt.Errorf("context cancelled before attempt %d: %w", i+1, ctx.Err())
		default:
		}

		// Créer une nouvelle requête pour chaque tentative (le body peut être consommé)
		req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
		if err != nil {
			return fmt.Errorf("failed to create request: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")

		// FIX #11 & #23: Propager le request_id depuis le contexte si disponible
		if requestID := extractRequestIDFromContext(ctx); requestID != "" {
			req.Header.Set("X-Request-ID", requestID)
			s.logger.Debug("Propagating request_id to stream server",
				zap.String("request_id", requestID),
				zap.String("track_id", trackID.String()),
			)
		}

		// MOD-P2-007: Utiliser circuit breaker pour protéger contre dépendances lentes
		resp, err := s.circuitBreaker.DoWithContext(ctx, req)
		if err != nil {
			s.logger.Warn("Stream server request failed, retrying",
				zap.Int("attempt", i+1),
				zap.Int("max_retries", maxRetries),
				zap.Error(err))

			if i < maxRetries-1 {
				// Attendre avec backoff exponentiel avant de réessayer
				select {
				case <-ctx.Done():
					return fmt.Errorf("context cancelled during backoff: %w", ctx.Err())
				case <-time.After(backoff):
					backoff *= 2 // Exponential backoff: 1s, 2s, 4s
				}
				continue
			}

			return fmt.Errorf("stream server request failed after %d attempts: %w", maxRetries, err)
		}

		defer resp.Body.Close()

		if resp.StatusCode == http.StatusOK {
			s.logger.Info("Started processing for track",
				zap.Any("track_id", trackID),
				zap.Int("attempt", i+1))
			return nil
		}

		// Status code non-OK : retry si possible
		s.logger.Warn("Stream server returned non-200 status",
			zap.Int("status", resp.StatusCode),
			zap.Int("attempt", i+1),
			zap.Int("max_retries", maxRetries))

		if i < maxRetries-1 {
			// Attendre avec backoff exponentiel avant de réessayer
			select {
			case <-ctx.Done():
				return fmt.Errorf("context cancelled during backoff: %w", ctx.Err())
			case <-time.After(backoff):
				backoff *= 2 // Exponential backoff: 1s, 2s, 4s
			}
		}
	}

	return fmt.Errorf("stream server returned non-200 status after %d attempts", maxRetries)
}

// extractRequestIDFromContext extrait le request_id depuis un contexte Go
// FIX #11: Utilise la même logique que middleware.GetRequestIDFromGoContext mais sans cycle d'import
func extractRequestIDFromContext(ctx context.Context) string {
	// Essayer différentes clés possibles
	keys := []interface{}{"request_id", "X-Request-ID", "requestId"}

	for _, key := range keys {
		if value := ctx.Value(key); value != nil {
			if id, ok := value.(string); ok && id != "" {
				return id
			}
		}
	}

	return ""
}
