package services

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

	"veza-backend-api/internal/metrics"

	"github.com/sony/gobreaker"
	"go.uber.org/zap"
)

// CircuitBreakerHTTPClient wraps an HTTP client with circuit breaker protection
// MOD-P2-007: Circuit breaker pour protéger contre dépendances lentes/indisponibles
type CircuitBreakerHTTPClient struct {
	client         *http.Client
	circuitBreaker *gobreaker.CircuitBreaker
	logger         *zap.Logger
}

// NewCircuitBreakerHTTPClient creates a new HTTP client with circuit breaker
// MOD-P2-007: Circuit breaker avec seuils configurables
func NewCircuitBreakerHTTPClient(client *http.Client, name string, logger *zap.Logger) *CircuitBreakerHTTPClient {
	if client == nil {
		client = &http.Client{Timeout: 10 * time.Second}
	}
	if logger == nil {
		logger = zap.NewNop()
	}

	// Configuration circuit breaker:
	// - MaxRequests: 3 requêtes simultanées max
	// - Interval: 60s pour réinitialiser les compteurs
	// - Timeout: 30s avant de passer en half-open
	// - ReadyToTrip: s'ouvre après 5 échecs consécutifs
	cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
		Name:        name,
		MaxRequests: 3,
		Interval:    60 * time.Second,
		Timeout:     30 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			return counts.ConsecutiveFailures >= 5
		},
		OnStateChange: func(cbName string, from gobreaker.State, to gobreaker.State) {
			logger.Info("Circuit breaker state changed",
				zap.String("name", cbName),
				zap.String("from", from.String()),
				zap.String("to", to.String()))
			// MOD-P2-007: Mettre à jour les métriques lors du changement d'état
			// Note: On ne peut pas accéder à cb ici car il n'est pas encore créé
			// Les métriques seront mises à jour dans Do() après chaque requête
		},
	})

	return &CircuitBreakerHTTPClient{
		client:         client,
		circuitBreaker: cb,
		logger:         logger,
	}
}

// Do executes an HTTP request with circuit breaker protection
// MOD-P2-007: Wrapper pour http.Client.Do avec circuit breaker
func (c *CircuitBreakerHTTPClient) Do(req *http.Request) (*http.Response, error) {
	// MOD-P2-007: Mettre à jour les métriques avant l'exécution
	counts := c.circuitBreaker.Counts()
	state := c.circuitBreaker.State()
	metrics.UpdateCircuitBreakerMetrics(c.circuitBreaker.Name(), counts, state)

	// Exécuter la requête via circuit breaker
	result, err := c.circuitBreaker.Execute(func() (interface{}, error) {
		resp, err := c.client.Do(req)
		if err != nil {
			// MOD-P2-007: Enregistrer l'échec dans les métriques
			metrics.RecordCircuitBreakerRequest(c.circuitBreaker.Name(), "failure")
			return nil, err
		}
		// Considérer les codes 5xx comme des erreurs pour le circuit breaker
		if resp.StatusCode >= 500 {
			resp.Body.Close()
			// MOD-P2-007: Enregistrer l'échec dans les métriques
			metrics.RecordCircuitBreakerRequest(c.circuitBreaker.Name(), "failure")
			return nil, fmt.Errorf("server error: %d", resp.StatusCode)
		}
		// MOD-P2-007: Enregistrer le succès dans les métriques
		metrics.RecordCircuitBreakerRequest(c.circuitBreaker.Name(), "success")
		return resp, nil
	})

	if err != nil {
		// Circuit breaker ouvert ou erreur HTTP
		if err == gobreaker.ErrOpenState {
			// MOD-P2-007: Enregistrer le rejet dans les métriques
			metrics.RecordCircuitBreakerRequest(c.circuitBreaker.Name(), "rejected")
			c.logger.Warn("Circuit breaker is open, request rejected",
				zap.String("circuit_breaker", c.circuitBreaker.Name()),
				zap.String("url", req.URL.String()))
			return nil, fmt.Errorf("circuit breaker is open: service unavailable")
		}
		return nil, err
	}

	// Type assertion pour récupérer la réponse
	if httpResp, ok := result.(*http.Response); ok {
		// MOD-P2-007: Mettre à jour les métriques après succès
		counts = c.circuitBreaker.Counts()
		state = c.circuitBreaker.State()
		metrics.UpdateCircuitBreakerMetrics(c.circuitBreaker.Name(), counts, state)
		return httpResp, nil
	}

	return nil, fmt.Errorf("unexpected response type from circuit breaker")
}

// DoWithContext executes an HTTP request with context and circuit breaker protection
// MOD-P2-007: Version avec contexte pour timeout/cancellation
func (c *CircuitBreakerHTTPClient) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	// Créer une nouvelle requête avec le contexte
	req = req.WithContext(ctx)
	return c.Do(req)
}
