package track

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

	"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/zaptest"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// setupTestTrackHandler creates a test handler with real services and in-memory database
func setupTestTrackHandler(t *testing.T) (*TrackHandler, *gorm.DB, *gin.Engine, 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
	err = db.AutoMigrate(
		&models.User{},
		&models.Track{},
		&models.TrackLike{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	// Setup services
	trackService := NewTrackService(db, logger, "/tmp/test-uploads")
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService("/tmp/test-chunks", nil, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("http://localhost:8082", logger)

	handler := NewTrackHandler(
		trackService,
		trackUploadService,
		chunkService,
		likeService,
		streamService,
	)

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

	cleanup := func() {
		// Database cleanup handled by test
	}

	return handler, db, router, cleanup
}

// Helper to create a test track
func createTestTrack(id uuid.UUID, userID uuid.UUID) *models.Track {
	return &models.Track{
		ID:        id,
		UserID:    userID,
		Title:     "Test Track",
		Artist:    "Test Artist",
		FilePath:  "/tmp/test-uploads/test.mp3",
		Format:    "mp3",
		FileSize:  1024,
		Duration:  180,
		IsPublic:  true,
		Status:    models.TrackStatusCompleted,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
}

// TestTrackHandler_GetTrack_Success tests successful track retrieval
func TestTrackHandler_GetTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create a test user first
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create a test track
	trackID := uuid.New()
	track := createTestTrack(trackID, userID)
	err = db.Create(track).Error
	require.NoError(t, err)

	router.GET("/tracks/:id", handler.GetTrack)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/tracks/%s", trackID.String()), nil)
	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))
}

// TestTrackHandler_GetTrack_NotFound tests track not found scenario
func TestTrackHandler_GetTrack_NotFound(t *testing.T) {
	handler, _, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	router.GET("/tracks/:id", handler.GetTrack)

	nonExistentID := uuid.New()
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/tracks/%s", nonExistentID.String()), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestTrackHandler_GetTrack_InvalidID tests invalid track ID format
func TestTrackHandler_GetTrack_InvalidID(t *testing.T) {
	handler, _, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	router.GET("/tracks/:id", handler.GetTrack)

	req := httptest.NewRequest(http.MethodGet, "/tracks/invalid-id", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestTrackHandler_ListTracks_Success tests successful track listing
func TestTrackHandler_ListTracks_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test user first
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	for i := 0; i < 3; i++ {
		track := createTestTrack(uuid.New(), userID)
		track.Title = fmt.Sprintf("Track %d", i+1)
		err := db.Create(track).Error
		require.NoError(t, err)
	}

	router.GET("/tracks", handler.ListTracks)

	req := httptest.NewRequest(http.MethodGet, "/tracks?page=1&limit=10", nil)
	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))
}

// TestTrackHandler_UpdateTrack_Success tests successful track update
func TestTrackHandler_UpdateTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test user and track
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	trackID := uuid.New()
	track := createTestTrack(trackID, userID)
	err = db.Create(track).Error
	require.NoError(t, err)

	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.PUT("/tracks/:id", handler.UpdateTrack)

	updateReq := UpdateTrackRequest{
		Title:  stringPtr("Updated Title"),
		Artist: stringPtr("Updated Artist"),
	}
	body, _ := json.Marshal(updateReq)
	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/tracks/%s", trackID.String()), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.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))
}

// TestTrackHandler_DeleteTrack_Success tests successful track deletion
func TestTrackHandler_DeleteTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test user and track
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	trackID := uuid.New()
	track := createTestTrack(trackID, userID)
	err = db.Create(track).Error
	require.NoError(t, err)

	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.DELETE("/tracks/:id", handler.DeleteTrack)

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/tracks/%s", trackID.String()), nil)
	req.Header.Set("X-User-ID", userID.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))
}

// TestTrackHandler_LikeTrack_Success tests successful track like
func TestTrackHandler_LikeTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test user first
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test track
	trackID := uuid.New()
	track := createTestTrack(trackID, userID)
	err = db.Create(track).Error
	require.NoError(t, err)

	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.POST("/tracks/:id/like", handler.LikeTrack)

	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tracks/%s/like", trackID.String()), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestTrackHandler_SearchTracks_Success tests successful track search
func TestTrackHandler_SearchTracks_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Setup search service
	searchService := services.NewTrackSearchService(db)
	handler.SetSearchService(searchService)

	// Create test user first
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
	}
	err := db.Create(user).Error
	require.NoError(t, err)

	// Create test tracks
	track1 := createTestTrack(uuid.New(), userID)
	track1.Title = "Test Song"
	err = db.Create(track1).Error
	require.NoError(t, err)

	router.GET("/tracks/search", handler.SearchTracks)

	req := httptest.NewRequest(http.MethodGet, "/tracks/search?q=Test&limit=10&offset=0", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// Helper function to create string pointer
func stringPtr(s string) *string {
	return &s
}
