//go:build integration
// +build integration

package handlers

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

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

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

// setupAPIFlowRouter creates a router with multiple handlers for E2E testing
func setupAPIFlowRouter(t *testing.T) (*gin.Engine, *gorm.DB, func()) {
	gin.SetMode(gin.TestMode)

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

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

	// Auto-migrate
	// Note: Add all models needed for the flow
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.Playlist{},
		&models.PlaylistTrack{},
		&models.TrackComment{},
		&models.BitrateAdaptationLog{},
	)
	require.NoError(t, err)

	// Setup logger
	logger := zap.NewNop()

	// --- Services ---
	playlistService := services.NewPlaylistServiceWithDB(db, logger)

	commentService := services.NewCommentService(db, logger)

	bandwidthService := services.NewBandwidthDetectionService(logger)
	bitrateService := services.NewBitrateAdaptationService(db, bandwidthService, logger)

	// --- Handlers ---
	playlistHandler := NewPlaylistHandler(playlistService, db, logger)
	commentHandler := NewCommentHandler(commentService, logger)
	bitrateHandler := NewBitrateHandler(bitrateService, logger)

	// Create router
	router := gin.New()
	// Middleware to simulate auth (extract user_id from header)
	authMiddleware := func(c *gin.Context) {
		if userIDStr := c.GetHeader("X-User-ID"); userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	}

	v1 := router.Group("/api/v1")
	v1.Use(authMiddleware)
	{
		// Playlist Routes
		v1.POST("/playlists", playlistHandler.CreatePlaylist)
		v1.GET("/playlists/:id", playlistHandler.GetPlaylist)
		v1.POST("/playlists/:id/tracks/:trackId", playlistHandler.AddTrack)

		// Comment Routes
		v1.POST("/tracks/:id/comments", commentHandler.CreateComment)
		v1.GET("/tracks/:id/comments", commentHandler.GetComments)
		v1.DELETE("/comments/:id", commentHandler.DeleteComment)

		// Bitrate Routes
		v1.POST("/tracks/:id/bitrate/adapt", bitrateHandler.AdaptBitrate)
	}

	cleanup := func() {
		// Close DB logic if needed, but in memory
	}

	return router, db, cleanup
}

func TestAPIFlow_UserJourney(t *testing.T) {
	router, db, cleanup := setupAPIFlowRouter(t)
	defer cleanup()

	// 1. Setup Data
	// Create User A (Artist)
	userA := &models.User{
		ID:       uuid.New(),
		Username: "artist_user",
		Email:    "artist@example.com",
		IsActive: true,
	}
	require.NoError(t, db.Create(userA).Error)

	// Create User B (Listener)
	userB := &models.User{
		ID:       uuid.New(),
		Username: "listener_user",
		Email:    "listener@example.com",
		IsActive: true,
	}
	require.NoError(t, db.Create(userB).Error)

	// User A uploads a Track
	track := &models.Track{
		ID:       uuid.New(),
		UserID:   userA.ID,
		Title:    "Awesome Song",
		FilePath: "/s3/bucket/key",
		Duration: 180,
		IsPublic: true,
	}
	require.NoError(t, db.Create(track).Error)

	// 2. User B adapts bitrate (Simulate streaming start)
	t.Run("Bitrate Adaptation Flow", func(t *testing.T) {
		reqBody := map[string]interface{}{
			"current_bitrate": 128,
			"bandwidth":       5000000, // 5 Mbps
			"buffer_level":    0.5,
		}
		jsonBody, _ := json.Marshal(reqBody)
		req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/tracks/%s/bitrate/adapt", track.ID), bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userB.ID.String())

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code, "Bitrate adaptation should return 200 OK")

		// Valider le contrat API: l'endpoint retourne recommended_bitrate
		var resp map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &resp)
		require.NoError(t, err, "Response should be valid JSON: %s", w.Body.String())

		// Vérifier que la réponse contient recommended_bitrate
		recommendedBitrate, ok := resp["recommended_bitrate"]
		require.True(t, ok, "Response should contain recommended_bitrate: %v", resp)

		// Vérifier que c'est un nombre valide
		bitrateFloat, ok := recommendedBitrate.(float64)
		require.True(t, ok, "recommended_bitrate should be a number: %v (type: %T)", recommendedBitrate, recommendedBitrate)

		// Vérifier que le bitrate recommandé est valide (> 0)
		assert.Greater(t, int(bitrateFloat), 0, "Recommended bitrate should be positive")

		// Avec 5 Mbps de bandwidth et buffer_level 0.5, on devrait recommander un bitrate > 128
		assert.GreaterOrEqual(t, int(bitrateFloat), 128, "With 5 Mbps bandwidth, should recommend >= 128 kbps")
	})

	// 3. User B comments on the track
	var commentIDStr string
	t.Run("Comment Flow", func(t *testing.T) {
		reqBody := map[string]interface{}{
			"content": "This song is fire!",
		}
		jsonBody, _ := json.Marshal(reqBody)
		req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/tracks/%s/comments", track.ID), bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userB.ID.String())

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		if !assert.Equal(t, http.StatusCreated, w.Code) {
			t.Logf("Response Body: %s", w.Body.String())
			return
		}

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)

		commentObj, ok := resp["comment"].(map[string]interface{})
		if !ok {
			t.Logf("Comment object missing in response: %v", resp)
			t.FailNow()
		}

		if id, ok := commentObj["id"].(string); ok {
			commentIDStr = id
		} else {
			t.Logf("ID missing in comment object: %v", commentObj)
		}

		assert.NotEmpty(t, commentIDStr)
		assert.Equal(t, "This song is fire!", commentObj["content"])
	})

	// 4. User A replies to User B's comment
	t.Run("Reply Flow", func(t *testing.T) {
		reqBody := map[string]interface{}{
			"content":   "Thanks!",
			"parent_id": commentIDStr,
		}
		jsonBody, _ := json.Marshal(reqBody)
		req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/tracks/%s/comments", track.ID), bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userA.ID.String())

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

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

		var resp map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &resp)

		commentObj, ok := resp["comment"].(map[string]interface{})
		require.True(t, ok, "Response should contain comment object")

		assert.Equal(t, "Thanks!", commentObj["content"])
		// ParentID might be nil in JSON if omitted, or present.
		// UUID string.
		assert.Equal(t, commentIDStr, commentObj["parent_id"])
	})

	// 5. User B tries to delete User A's reply (Unauthorized)
	t.Run("Unauthorized Delete Flow", func(t *testing.T) {
		// Need User A's reply ID.
		// We'll fetch comments first to get it, or simpler:
		// Just creating a dummy interaction or checking previous response.
		// Let's assume we grabbed it from previous step response.
		// (Actually strict testing requires capturing it).

		// Let's re-run reply creation capture
		// OR just query DB to get the reply ID.
		var reply models.TrackComment
		db.Where("user_id = ?", userA.ID).First(&reply)

		req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/comments/%s", reply.ID), nil)
		req.Header.Set("X-User-ID", userB.ID.String()) // User B trying to delete A's comment

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusForbidden, w.Code)
		var resp map[string]string
		json.Unmarshal(w.Body.Bytes(), &resp)
		// Expect "unauthorized: you can only delete your own comments"
		// Which is handled by services.ErrForbidden now -> 403
		assert.Contains(t, resp["error"], "unauthorized")
	})

	// 6. User B creates a Playlist and adds the track
	var playlistIDStr string
	t.Run("Playlist Flow", func(t *testing.T) {
		// Create Playlist
		reqBody := map[string]interface{}{
			"title":     "My Favorites",
			"is_public": false,
		}
		jsonBody, _ := json.Marshal(reqBody)
		req, _ := http.NewRequest("POST", "/api/v1/playlists", bytes.NewBuffer(jsonBody))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userB.ID.String())

		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		if !assert.Equal(t, http.StatusCreated, w.Code) {
			t.Logf("Create Playlist Response Body: %s", w.Body.String())
			t.FailNow()
		}
		var resp map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &resp)
		require.NoError(t, err)

		t.Logf("Playlist Created: %v", resp)

		// Le format standardisé retourne data.playlist
		data, ok := resp["data"].(map[string]interface{})
		require.True(t, ok, "Response should have data object: %v", resp)

		playlistObj, ok := data["playlist"].(map[string]interface{})
		require.True(t, ok, "Data should contain playlist object: %v", data)

		if id, ok := playlistObj["id"].(string); ok {
			playlistIDStr = id
		} else {
			t.Logf("ID missing in playlist object: %v", playlistObj)
			t.FailNow()
		}

		// Add Track (User A's track) to Playlist (User B's playlist)
		// Handler expects trackID in URL: POST /playlists/:id/tracks/:trackId
		req2, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/playlists/%s/tracks/%s", playlistIDStr, track.ID.String()), nil)
		req2.Header.Set("X-User-ID", userB.ID.String())

		w2 := httptest.NewRecorder()
		router.ServeHTTP(w2, req2)

		if !assert.Equal(t, http.StatusOK, w2.Code) {
			t.Logf("Add Track Response: %s", w2.Body.String())
		}
	})
}
