//go:build integration || marketplace
// +build integration marketplace

package marketplace

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

	"veza-backend-api/internal/core/marketplace"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/handlers"
	"veza-backend-api/internal/models"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// MockStorageService is a mock implementation of StorageService
type MockStorageService struct {
	GetDownloadURLFunc func(filePath string) (string, error)
}

func (m *MockStorageService) GetDownloadURL(ctx context.Context, filePath string) (string, error) {
	if m.GetDownloadURLFunc != nil {
		return m.GetDownloadURLFunc(filePath)
	}
	return fmt.Sprintf("/download/%s", filePath), nil
}

// setupMarketplaceFlowTestRouter crée un router de test avec les services nécessaires pour les tests de flux marketplace
func setupMarketplaceFlowTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *database.Database, *marketplace.Service, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate models (User and Track use standard GORM)
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
	)
	require.NoError(t, err)

	// Create marketplace tables manually for SQLite compatibility (gen_random_uuid() is PostgreSQL-specific)
	// Products table
	err = db.Exec(`
		CREATE TABLE IF NOT EXISTS products (
			id TEXT PRIMARY KEY,
			seller_id TEXT NOT NULL,
			title TEXT NOT NULL,
			description TEXT,
			price REAL NOT NULL,
			currency TEXT DEFAULT 'EUR',
			status TEXT DEFAULT 'draft',
			product_type TEXT NOT NULL,
			track_id TEXT,
			license_type TEXT,
			created_at DATETIME,
			updated_at DATETIME,
			deleted_at DATETIME
		)
	`).Error
	require.NoError(t, err)

	// Orders table
	err = db.Exec(`
		CREATE TABLE IF NOT EXISTS orders (
			id TEXT PRIMARY KEY,
			buyer_id TEXT NOT NULL,
			total_amount REAL NOT NULL,
			currency TEXT DEFAULT 'EUR',
			status TEXT DEFAULT 'pending',
			payment_intent TEXT,
			created_at DATETIME,
			updated_at DATETIME
		)
	`).Error
	require.NoError(t, err)

	// Order items table
	err = db.Exec(`
		CREATE TABLE IF NOT EXISTS order_items (
			id TEXT PRIMARY KEY,
			order_id TEXT NOT NULL,
			product_id TEXT NOT NULL,
			price REAL NOT NULL,
			FOREIGN KEY(order_id) REFERENCES orders(id) ON DELETE CASCADE
		)
	`).Error
	require.NoError(t, err)

	// Licenses table
	err = db.Exec(`
		CREATE TABLE IF NOT EXISTS licenses (
			id TEXT PRIMARY KEY,
			buyer_id TEXT NOT NULL,
			track_id TEXT NOT NULL,
			product_id TEXT NOT NULL,
			order_id TEXT NOT NULL,
			type TEXT NOT NULL,
			rights TEXT,
			downloads_left INTEGER DEFAULT 3,
			created_at DATETIME,
			expires_at DATETIME
		)
	`).Error
	require.NoError(t, err)

	// Add BeforeCreate hooks for SQLite compatibility to generate UUIDs
	db.Callback().Create().Before("gorm:create").Register("generate_uuid_product", func(db *gorm.DB) {
		if product, ok := db.Statement.Dest.(*marketplace.Product); ok && product.ID == uuid.Nil {
			product.ID = uuid.New()
		}
		if products, ok := db.Statement.Dest.(*[]marketplace.Product); ok {
			for i := range *products {
				if (*products)[i].ID == uuid.Nil {
					(*products)[i].ID = uuid.New()
				}
			}
		}
	})
	db.Callback().Create().Before("gorm:create").Register("generate_uuid_order", func(db *gorm.DB) {
		if order, ok := db.Statement.Dest.(*marketplace.Order); ok && order.ID == uuid.Nil {
			order.ID = uuid.New()
		}
		if orders, ok := db.Statement.Dest.(*[]marketplace.Order); ok {
			for i := range *orders {
				if (*orders)[i].ID == uuid.Nil {
					(*orders)[i].ID = uuid.New()
				}
			}
		}
	})
	db.Callback().Create().Before("gorm:create").Register("generate_uuid_order_item", func(db *gorm.DB) {
		if item, ok := db.Statement.Dest.(*marketplace.OrderItem); ok && item.ID == uuid.Nil {
			item.ID = uuid.New()
		}
		if items, ok := db.Statement.Dest.(*[]marketplace.OrderItem); ok {
			for i := range *items {
				if (*items)[i].ID == uuid.Nil {
					(*items)[i].ID = uuid.New()
				}
			}
		}
	})
	db.Callback().Create().Before("gorm:create").Register("generate_uuid_license", func(db *gorm.DB) {
		if license, ok := db.Statement.Dest.(*marketplace.License); ok && license.ID == uuid.Nil {
			license.ID = uuid.New()
		}
		if licenses, ok := db.Statement.Dest.(*[]marketplace.License); ok {
			for i := range *licenses {
				if (*licenses)[i].ID == uuid.Nil {
					(*licenses)[i].ID = uuid.New()
				}
			}
		}
	})

	// Get underlying sql.DB from GORM for raw SQL queries
	sqlDB, err := db.DB()
	require.NoError(t, err)

	dbWrapper := &database.Database{
		DB:     sqlDB,
		GormDB: db,
		Logger: logger,
	}

	// Setup storage service
	storageService := &MockStorageService{
		GetDownloadURLFunc: func(filePath string) (string, error) {
			return fmt.Sprintf("https://storage.example.com/download/%s", filePath), nil
		},
	}

	// Setup marketplace service
	marketService := marketplace.NewService(db, logger, storageService)

	// Setup handlers
	marketHandler := handlers.NewMarketplaceHandler(marketService, logger)

	// Create router
	router := gin.New()

	// Mock auth middleware - set user_id from header if present
	router.Use(func(c *gin.Context) {
		if userIDStr := c.GetHeader("X-User-ID"); userIDStr != "" {
			if userID, err := uuid.Parse(userIDStr); err == nil {
				c.Set("user_id", userID)
			}
		}
		c.Next()
	})

	// Setup marketplace routes
	marketplaceGroup := router.Group("/api/v1/marketplace")
	{
		marketplaceGroup.GET("/products", marketHandler.ListProducts)
		marketplaceGroup.POST("/products", marketHandler.CreateProduct)
		marketplaceGroup.PUT("/products/:id", marketHandler.UpdateProduct)
		marketplaceGroup.GET("/orders", marketHandler.ListOrders)
		marketplaceGroup.GET("/orders/:id", marketHandler.GetOrder)
		marketplaceGroup.POST("/orders", marketHandler.CreateOrder)
		marketplaceGroup.GET("/download/:product_id", marketHandler.GetDownloadURL)
	}

	cleanup := func() {
		// Database will be closed automatically
	}

	return router, db, dbWrapper, marketService, cleanup
}

// createTestUser crée un utilisateur de test
func createTestUser(t *testing.T, db *gorm.DB, email, username string) *models.User {
	user := &models.User{
		ID:           uuid.New(),
		Username:     username,
		Email:        email,
		PasswordHash: "hashed_password",
		Slug:         username,
		IsActive:     true,
		IsVerified:   true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

// createTestTrack crée un track de test
func createTestTrack(t *testing.T, db *gorm.DB, userID uuid.UUID, title string) *models.Track {
	track := &models.Track{
		ID:        uuid.New(),
		UserID:    userID,
		Title:     title,
		Artist:    "Test Artist",
		FilePath:    fmt.Sprintf("/tracks/%s.mp3", uuid.New().String()),
		Format:    "mp3",
		FileSize:  5 * 1024 * 1024,
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err := db.Create(track).Error
	require.NoError(t, err)
	return track
}

// TestMarketplaceFlow_CompleteFlow teste le flux complet : création produit → commande → téléchargement
func TestMarketplaceFlow_CompleteFlow(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	// Create seller and buyer
	seller := createTestUser(t, db, "seller@example.com", "seller")
	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	// Create track owned by seller
	track := createTestTrack(t, db, seller.ID, "My Awesome Track")

	// Step 1: Seller creates a product
	createProductReq := handlers.CreateProductRequest{
		Title:       "My Product",
		Description: "A great track for sale",
		Price:       9.99,
		ProductType: "track",
		TrackID:     track.ID.String(),
		LicenseType: "standard",
	}
	body, _ := json.Marshal(createProductReq)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/products", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", seller.ID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	var productResponse map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &productResponse)
	require.NoError(t, err)
	assert.True(t, productResponse["success"].(bool))

	// Extract product ID from response
	productData, ok := productResponse["data"].(map[string]interface{})
	require.True(t, ok)
	productIDStr, ok := productData["id"].(string)
	require.True(t, ok)
	productID, err := uuid.Parse(productIDStr)
	require.NoError(t, err)

	// Step 2: Buyer creates an order for the product
	createOrderReq := handlers.CreateOrderRequest{
		Items: []struct {
			ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
		}{
			{ProductID: productID.String()},
		},
	}
	body, _ = json.Marshal(createOrderReq)
	req = httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", buyer.ID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	var orderResponse map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &orderResponse)
	require.NoError(t, err)
	assert.True(t, orderResponse["success"].(bool))

	// Verify order was created with correct status
	orderData, ok := orderResponse["data"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, "completed", orderData["status"])

	// Step 3: Buyer gets download URL for the purchased product
	req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/marketplace/download/%s", productID.String()), nil)
	req.Header.Set("X-User-ID", buyer.ID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var downloadResponse map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &downloadResponse)
	require.NoError(t, err)
	assert.True(t, downloadResponse["success"].(bool))

	// Verify download URL is present
	downloadData, ok := downloadResponse["data"].(map[string]interface{})
	require.True(t, ok)
	assert.Contains(t, downloadData, "url")
	assert.NotEmpty(t, downloadData["url"])
}

// TestMarketplaceFlow_CreateProduct_Validation teste la validation lors de la création de produit
func TestMarketplaceFlow_CreateProduct_Validation(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	seller := createTestUser(t, db, "seller@example.com", "seller")

	t.Run("Missing required fields", func(t *testing.T) {
		createProductReq := handlers.CreateProductRequest{
			Description: "A great track",
			Price:       9.99,
		}
		body, _ := json.Marshal(createProductReq)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/products", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", seller.ID.String())
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

	t.Run("Invalid price", func(t *testing.T) {
		createProductReq := handlers.CreateProductRequest{
			Title:       "My Product",
			Description: "A great track",
			Price:       -5.0, // Invalid negative price
			ProductType: "track",
		}
		body, _ := json.Marshal(createProductReq)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/products", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", seller.ID.String())
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

	t.Run("Invalid product type", func(t *testing.T) {
		createProductReq := handlers.CreateProductRequest{
			Title:       "My Product",
			Description: "A great track",
			Price:       9.99,
			ProductType: "invalid_type",
		}
		body, _ := json.Marshal(createProductReq)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/products", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", seller.ID.String())
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

// TestMarketplaceFlow_CreateOrder_Validation teste la validation lors de la création de commande
func TestMarketplaceFlow_CreateOrder_Validation(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	t.Run("Empty items", func(t *testing.T) {
		createOrderReq := handlers.CreateOrderRequest{
			Items: []struct {
				ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
			}{},
		}
		body, _ := json.Marshal(createOrderReq)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", buyer.ID.String())
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

	t.Run("Non-existent product", func(t *testing.T) {
		createOrderReq := handlers.CreateOrderRequest{
			Items: []struct {
				ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
			}{
				{ProductID: uuid.New().String()},
			},
		}
		body, _ := json.Marshal(createOrderReq)
		req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", buyer.ID.String())
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

// TestMarketplaceFlow_GetDownloadURL_NoLicense teste le téléchargement sans licence
func TestMarketplaceFlow_GetDownloadURL_NoLicense(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	seller := createTestUser(t, db, "seller@example.com", "seller")
	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	// Create track and product
	track := createTestTrack(t, db, seller.ID, "My Track")
	product := &marketplace.Product{
		ID:          uuid.New(),
		SellerID:    seller.ID,
		Title:       "My Product",
		Description: "A great track",
		Price:       9.99,
		ProductType: "track",
		TrackID:     &track.ID,
		Status:      marketplace.ProductStatusActive,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(product).Error
	require.NoError(t, err)

	// Try to download without purchasing
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/marketplace/download/%s", product.ID.String()), nil)
	req.Header.Set("X-User-ID", buyer.ID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestMarketplaceFlow_ListOrders teste la liste des commandes
func TestMarketplaceFlow_ListOrders(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	seller := createTestUser(t, db, "seller@example.com", "seller")
	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	// Create track and product
	track := createTestTrack(t, db, seller.ID, "My Track")
	product := &marketplace.Product{
		ID:          uuid.New(),
		SellerID:    seller.ID,
		Title:       "My Product",
		Description: "A great track",
		Price:       9.99,
		ProductType: "track",
		TrackID:     &track.ID,
		Status:      marketplace.ProductStatusActive,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(product).Error
	require.NoError(t, err)

	// Create order
	createOrderReq := handlers.CreateOrderRequest{
		Items: []struct {
			ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
		}{
			{ProductID: product.ID.String()},
		},
	}
	body, _ := json.Marshal(createOrderReq)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", buyer.ID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// List orders
	req = httptest.NewRequest(http.MethodGet, "/api/v1/marketplace/orders", nil)
	req.Header.Set("X-User-ID", buyer.ID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))

	// Verify orders list
	data, ok := response["data"].([]interface{})
	require.True(t, ok)
	assert.GreaterOrEqual(t, len(data), 1)
}

// TestMarketplaceFlow_GetOrder teste la récupération d'une commande spécifique
func TestMarketplaceFlow_GetOrder(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	seller := createTestUser(t, db, "seller@example.com", "seller")
	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	// Create track and product
	track := createTestTrack(t, db, seller.ID, "My Track")
	product := &marketplace.Product{
		ID:          uuid.New(),
		SellerID:    seller.ID,
		Title:       "My Product",
		Description: "A great track",
		Price:       9.99,
		ProductType: "track",
		TrackID:     &track.ID,
		Status:      marketplace.ProductStatusActive,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(product).Error
	require.NoError(t, err)

	// Create order
	createOrderReq := handlers.CreateOrderRequest{
		Items: []struct {
			ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
		}{
			{ProductID: product.ID.String()},
		},
	}
	body, _ := json.Marshal(createOrderReq)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", buyer.ID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	// Extract order ID
	var orderResponse map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &orderResponse)
	require.NoError(t, err)
	orderData, ok := orderResponse["data"].(map[string]interface{})
	require.True(t, ok)
	orderIDStr, ok := orderData["id"].(string)
	require.True(t, ok)

	// Get order details
	req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/marketplace/orders/%s", orderIDStr), nil)
	req.Header.Set("X-User-ID", buyer.ID.String())
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))

	// Verify order details
	orderDetails, ok := response["data"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, orderIDStr, orderDetails["id"].(string))
	assert.Equal(t, "completed", orderDetails["status"])
}

// TestMarketplaceFlow_CreateOrder_InactiveProduct teste la commande d'un produit inactif
func TestMarketplaceFlow_CreateOrder_InactiveProduct(t *testing.T) {
	router, db, _, _, cleanup := setupMarketplaceFlowTestRouter(t)
	defer cleanup()

	seller := createTestUser(t, db, "seller@example.com", "seller")
	buyer := createTestUser(t, db, "buyer@example.com", "buyer")

	// Create track and inactive product
	track := createTestTrack(t, db, seller.ID, "My Track")
	product := &marketplace.Product{
		ID:          uuid.New(),
		SellerID:    seller.ID,
		Title:       "My Product",
		Description: "A great track",
		Price:       9.99,
		ProductType: "track",
		TrackID:     &track.ID,
		Status:      marketplace.ProductStatusDraft, // Inactive
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	err := db.Create(product).Error
	require.NoError(t, err)

	// Try to create order for inactive product
	createOrderReq := handlers.CreateOrderRequest{
		Items: []struct {
			ProductID string `json:"product_id" binding:"required" validate:"required,uuid"`
		}{
			{ProductID: product.ID.String()},
		},
	}
	body, _ := json.Marshal(createOrderReq)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/marketplace/orders", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", buyer.ID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

