package services

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/sony/gobreaker"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
)

// TestCircuitBreakerIntegration_5xxSimulation simule un scénario réel où un service externe
// retourne des erreurs 5xx, déclenchant l'ouverture du circuit breaker
// MOD-P2-007: Test d'intégration pour valider le déclenchement avec erreurs 5xx
func TestCircuitBreakerIntegration_5xxSimulation(t *testing.T) {
	logger := zaptest.NewLogger(t)

	// Mock server qui retourne 500 pour les 5 premières requêtes, puis 200
	requestCount := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestCount++
		if requestCount <= 5 {
			// Retourner 500 pour les 5 premières requêtes
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Internal Server Error"))
		} else {
			// Retourner 200 après
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		}
	}))
	defer server.Close()

	// Créer un circuit breaker avec seuil bas pour tester rapidement
	client := &http.Client{Timeout: 5 * time.Second}
	cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
		Name:        "integration-test",
		MaxRequests: 3,
		Interval:    1 * time.Second,
		Timeout:     1 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			return counts.ConsecutiveFailures >= 5 // S'ouvre après 5 échecs
		},
	})

	cbClient := &CircuitBreakerHTTPClient{
		client:         client,
		circuitBreaker: cb,
		logger:         logger,
	}

	// Phase 1: Faire 5 requêtes qui échouent (500)
	t.Log("Phase 1: Simuler 5 erreurs 5xx")
	for i := 0; i < 5; i++ {
		req, err := http.NewRequest("GET", server.URL, nil)
		require.NoError(t, err)

		resp, err := cbClient.Do(req)
		assert.Error(t, err, fmt.Sprintf("Request %d should fail", i+1))
		assert.Contains(t, err.Error(), "server error: 500")
		if resp != nil {
			resp.Body.Close()
		}
	}

	// Vérifier que le circuit breaker est maintenant ouvert
	time.Sleep(100 * time.Millisecond)
	state := cbClient.circuitBreaker.State()
	assert.Equal(t, gobreaker.StateOpen, state, "Circuit breaker should be open after 5 failures")
	t.Logf("Circuit breaker state: %v (expected: Open)", state)

	// Phase 2: Tenter une requête - devrait être rejetée immédiatement
	t.Log("Phase 2: Vérifier que les requêtes sont rejetées quand circuit ouvert")
	req, err := http.NewRequest("GET", server.URL, nil)
	require.NoError(t, err)

	resp, err := cbClient.Do(req)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "circuit breaker is open")
	assert.Nil(t, resp, "Response should be nil when circuit is open")
	t.Log("Request correctly rejected when circuit is open")

	// Phase 3: Attendre le timeout pour passer en half-open
	t.Log("Phase 3: Attendre timeout pour passer en half-open")
	time.Sleep(1100 * time.Millisecond) // Attendre un peu plus que le timeout (1s)

	state = cbClient.circuitBreaker.State()
	assert.True(t, state == gobreaker.StateHalfOpen || state == gobreaker.StateOpen,
		fmt.Sprintf("Expected HalfOpen or Open after timeout, got %v", state))
	t.Logf("Circuit breaker state after timeout: %v", state)

	// Phase 4: Si half-open, une requête réussie devrait permettre au circuit de se fermer
	// Note: gobreaker peut nécessiter plusieurs succès consécutifs pour fermer complètement
	if state == gobreaker.StateHalfOpen {
		t.Log("Phase 4: Tester half-open avec requête réussie")
		req, err = http.NewRequest("GET", server.URL, nil)
		require.NoError(t, err)

		// Le serveur retourne maintenant 200 (requestCount > 5)
		resp, err = cbClient.Do(req)
		require.NoError(t, err, "Request should succeed when server returns 200")
		assert.NotNil(t, resp)
		assert.Equal(t, http.StatusOK, resp.StatusCode)
		resp.Body.Close()

		// Vérifier que le circuit est en half-open ou fermé après succès
		// (gobreaker peut nécessiter plusieurs succès pour fermer complètement)
		time.Sleep(100 * time.Millisecond)
		finalState := cbClient.circuitBreaker.State()
		assert.True(t, finalState == gobreaker.StateHalfOpen || finalState == gobreaker.StateClosed,
			fmt.Sprintf("Circuit should be half-open or closed after successful request, got %v", finalState))
		t.Logf("Circuit breaker state after success: %v (half-open or closed is acceptable)", finalState)
	}
}

// TestCircuitBreakerIntegration_MetricsValidation valide que les métriques sont mises à jour
// MOD-P2-007: Test pour vérifier que les métriques Prometheus sont correctement enregistrées
func TestCircuitBreakerIntegration_MetricsValidation(t *testing.T) {
	logger := zaptest.NewLogger(t)

	// Mock server qui retourne 500
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	client := &http.Client{Timeout: 5 * time.Second}
	cbClient := NewCircuitBreakerHTTPClient(client, "metrics-test", logger)

	// Faire quelques requêtes qui échouent
	for i := 0; i < 3; i++ {
		req, _ := http.NewRequest("GET", server.URL, nil)
		cbClient.Do(req)
	}

	// Vérifier que les métriques ont été mises à jour
	// (On ne peut pas lire directement les métriques Prometheus, mais on vérifie qu'il n'y a pas d'erreur)
	counts := cbClient.circuitBreaker.Counts()
	assert.Greater(t, counts.TotalFailures, uint32(0), "Should have recorded failures")
	assert.Greater(t, counts.ConsecutiveFailures, uint32(0), "Should have consecutive failures")
	t.Logf("Metrics: TotalFailures=%d, ConsecutiveFailures=%d", counts.TotalFailures, counts.ConsecutiveFailures)
}
