package common

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
)

// TestRequest est un DTO de test avec validation
type TestRequest struct {
	Title    string    `json:"title" binding:"required,min=1,max=255" validate:"required,min=1,max=255"`
	Year     int       `json:"year" binding:"omitempty,min=1900,max=2100" validate:"omitempty,min=1900,max=2100"`
	TrackID  uuid.UUID `json:"track_id" binding:"required" validate:"required,uuid"`
	IsPublic *bool     `json:"is_public"`
}

// TestBindAndValidateJSON_ValidRequest vérifie qu'une requête valide passe la validation
func TestBindAndValidateJSON_ValidRequest(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true, "title": req.Title})
	})

	trackID := uuid.New()
	reqBody := map[string]interface{}{
		"title":     "Test Track",
		"year":      2020,
		"track_id":  trackID.String(),
		"is_public": true,
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &response)
	assert.Equal(t, true, response["success"])
}

// TestBindAndValidateJSON_MissingRequiredField vérifie qu'un champ requis manquant est rejeté
func TestBindAndValidateJSON_MissingRequiredField(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	reqBody := map[string]interface{}{
		"year": 2020,
		// title manquant
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_InvalidUUID vérifie qu'un UUID invalide est rejeté
func TestBindAndValidateJSON_InvalidUUID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	reqBody := map[string]interface{}{
		"title":    "Test Track",
		"track_id": "invalid-uuid",
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_StringTooLong vérifie qu'une string trop longue est rejetée
func TestBindAndValidateJSON_StringTooLong(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	trackID := uuid.New()
	reqBody := map[string]interface{}{
		"title":    string(make([]byte, 300)), // 300 caractères > max 255
		"track_id": trackID.String(),
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_NumberOutOfRange vérifie qu'un nombre hors limites est rejeté
func TestBindAndValidateJSON_NumberOutOfRange(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	trackID := uuid.New()
	reqBody := map[string]interface{}{
		"title":    "Test Track",
		"year":     1800, // < min 1900
		"track_id": trackID.String(),
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_EmptyString vérifie qu'une string vide est rejetée pour un champ required
func TestBindAndValidateJSON_EmptyString(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	trackID := uuid.New()
	reqBody := map[string]interface{}{
		"title":    "", // String vide
		"track_id": trackID.String(),
	}

	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_InvalidJSON vérifie qu'un JSON malformé est rejeté
func TestBindAndValidateJSON_InvalidJSON(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	invalidJSON := []byte(`{"title": "Test", "track_id": invalid}`)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(invalidJSON))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// TestBindAndValidateJSON_BodyTooLarge vérifie qu'un body trop gros est rejeté
func TestBindAndValidateJSON_BodyTooLarge(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.POST("/test", func(c *gin.Context) {
		var req TestRequest
		if !BindAndValidateJSON(c, &req) {
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	// Créer un body > 10MB
	largeBody := make([]byte, MaxJSONBodySize+1)
	req := httptest.NewRequest("POST", "/test", bytes.NewBuffer(largeBody))
	req.Header.Set("Content-Type", "application/json")
	req.ContentLength = int64(len(largeBody))
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}
