package handlers

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

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"
	"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"
)

// setupTestPlaylistHandler creates a test handler with real services and in-memory database
func setupTestPlaylistHandler(t *testing.T) (*PlaylistHandler, *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.Playlist{},
		&models.PlaylistTrack{},
		&models.PlaylistCollaborator{},
		&models.Role{},
		&models.Permission{},
		&models.UserRole{},
		&models.RolePermission{},
	)
	require.NoError(t, err)

	// Setup repositories
	playlistRepo := repositories.NewPlaylistRepository(db)
	playlistTrackRepo := repositories.NewPlaylistTrackRepository(db)
	playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db)
	userRepo := repositories.NewGormUserRepository(db)

	// Setup services
	playlistService := services.NewPlaylistService(
		playlistRepo,
		playlistTrackRepo,
		playlistCollaboratorRepo,
		userRepo,
		logger,
	)

	handler := NewPlaylistHandler(playlistService, db, logger)

	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 user
func createTestUser(id uuid.UUID) *models.User {
	return &models.User{
		ID:       id,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
}

// Helper to create a test playlist
func createTestPlaylist(id uuid.UUID, userID uuid.UUID) *models.Playlist {
	return &models.Playlist{
		ID:          id,
		UserID:      userID,
		Title:       "Test Playlist",
		Description: "Test Description",
		IsPublic:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
}

// Helper to create a test track for playlist tests
func createTestTrackForPlaylistTest(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(),
	}
}

// TestPlaylistHandler_CreatePlaylist_Success tests successful playlist creation
func TestPlaylistHandler_CreatePlaylist_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user first
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	router.POST("/playlists", handler.CreatePlaylist)

	createReq := CreatePlaylistRequest{
		Title:       "My New Playlist",
		Description: "A test playlist",
		IsPublic:    true,
	}
	body, _ := json.Marshal(createReq)
	req := httptest.NewRequest(http.MethodPost, "/playlists", 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.StatusCreated, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}

// TestPlaylistHandler_GetPlaylist_Success tests successful playlist retrieval
func TestPlaylistHandler_GetPlaylist_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user and playlist
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, userID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	router.GET("/playlists/:id", handler.GetPlaylist)

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

// TestPlaylistHandler_GetPlaylist_NotFound tests playlist not found scenario
func TestPlaylistHandler_GetPlaylist_NotFound(t *testing.T) {
	handler, _, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	router.GET("/playlists/:id", handler.GetPlaylist)

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

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

// TestPlaylistHandler_GetPlaylist_InvalidID tests invalid playlist ID format
func TestPlaylistHandler_GetPlaylist_InvalidID(t *testing.T) {
	handler, _, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	router.GET("/playlists/:id", handler.GetPlaylist)

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

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

// TestPlaylistHandler_GetPlaylists_Success tests successful playlist listing
func TestPlaylistHandler_GetPlaylists_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user first
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

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

	router.GET("/playlists", handler.GetPlaylists)

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

// TestPlaylistHandler_UpdatePlaylist_Success tests successful playlist update
func TestPlaylistHandler_UpdatePlaylist_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user and playlist
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, userID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	router.PUT("/playlists/:id", handler.UpdatePlaylist)

	title := "Updated Title"
	updateReq := UpdatePlaylistRequest{
		Title: &title,
	}
	body, _ := json.Marshal(updateReq)
	req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/playlists/%s", playlistID.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))
}

// TestPlaylistHandler_DeletePlaylist_Success tests successful playlist deletion
func TestPlaylistHandler_DeletePlaylist_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user and playlist
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, userID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	router.DELETE("/playlists/:id", handler.DeletePlaylist)

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/playlists/%s", playlistID.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))
}

// TestPlaylistHandler_AddTrack_Success tests successful track addition to playlist
func TestPlaylistHandler_AddTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user, playlist, and track
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, userID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

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

	router.POST("/playlists/:id/tracks/:trackId", handler.AddTrack)

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

// TestPlaylistHandler_RemoveTrack_Success tests successful track removal from playlist
func TestPlaylistHandler_RemoveTrack_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test user, playlist, and track
	userID := uuid.New()
	user := createTestUser(userID)
	err := db.Create(user).Error
	require.NoError(t, err)

	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, userID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

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

	// Add track to playlist first
	playlistTrack := &models.PlaylistTrack{
		ID:         uuid.New(),
		PlaylistID: playlistID,
		TrackID:    trackID,
		Position:   0,
		AddedBy:    userID,
	}
	err = db.Create(playlistTrack).Error
	require.NoError(t, err)

	router.DELETE("/playlists/:id/tracks/:trackId", handler.RemoveTrack)

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

// TestPlaylistHandler_AddCollaborator_Success tests successful collaborator addition
func TestPlaylistHandler_AddCollaborator_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test users
	ownerID := uuid.New()
	owner := createTestUser(ownerID)
	err := db.Create(owner).Error
	require.NoError(t, err)

	collaboratorID := uuid.New()
	collaborator := createTestUser(collaboratorID)
	collaborator.Username = "collaborator"
	collaborator.Email = "collaborator@example.com"
	err = db.Create(collaborator).Error
	require.NoError(t, err)

	// Create playlist
	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, ownerID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	router.POST("/playlists/:id/collaborators", handler.AddCollaborator)

	addReq := AddCollaboratorRequest{
		UserID:     collaboratorID,
		Permission: "write",
	}
	body, _ := json.Marshal(addReq)
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/playlists/%s/collaborators", playlistID.String()), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", ownerID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestPlaylistHandler_GetCollaborators_Success tests successful collaborator listing
func TestPlaylistHandler_GetCollaborators_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test users
	ownerID := uuid.New()
	owner := createTestUser(ownerID)
	err := db.Create(owner).Error
	require.NoError(t, err)

	collaboratorID := uuid.New()
	collaborator := createTestUser(collaboratorID)
	collaborator.Username = "collaborator"
	collaborator.Email = "collaborator@example.com"
	err = db.Create(collaborator).Error
	require.NoError(t, err)

	// Create playlist
	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, ownerID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	// Add collaborator
	playlistCollaborator := &models.PlaylistCollaborator{
		PlaylistID: playlistID,
		UserID:     collaboratorID,
		Permission: models.PlaylistPermissionWrite,
		CreatedAt:  time.Now(),
	}
	err = db.Create(playlistCollaborator).Error
	require.NoError(t, err)

	router.GET("/playlists/:id/collaborators", handler.GetCollaborators)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/playlists/%s/collaborators", playlistID.String()), nil)
	req.Header.Set("X-User-ID", ownerID.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))
}

// TestPlaylistHandler_RemoveCollaborator_Success tests successful collaborator removal
func TestPlaylistHandler_RemoveCollaborator_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestPlaylistHandler(t)
	defer cleanup()

	// Create test users
	ownerID := uuid.New()
	owner := createTestUser(ownerID)
	err := db.Create(owner).Error
	require.NoError(t, err)

	collaboratorID := uuid.New()
	collaborator := createTestUser(collaboratorID)
	collaborator.Username = "collaborator"
	collaborator.Email = "collaborator@example.com"
	err = db.Create(collaborator).Error
	require.NoError(t, err)

	// Create playlist
	playlistID := uuid.New()
	playlist := createTestPlaylist(playlistID, ownerID)
	err = db.Create(playlist).Error
	require.NoError(t, err)

	// Add collaborator
	playlistCollaborator := &models.PlaylistCollaborator{
		PlaylistID: playlistID,
		UserID:     collaboratorID,
		Permission: models.PlaylistPermissionWrite,
		CreatedAt:  time.Now(),
	}
	err = db.Create(playlistCollaborator).Error
	require.NoError(t, err)

	router.DELETE("/playlists/:id/collaborators/:userId", handler.RemoveCollaborator)

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

