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"
)

// TestTrackHandler_GetTrackStats tests GetTrackStats handler
func TestTrackHandler_GetTrackStats(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.GET("/tracks/:id/stats", handler.GetTrackStats)

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

	// GetTrackStats is currently a stub that returns NotImplemented
	assert.Equal(t, http.StatusNotImplemented, w.Code)
}

// TestTrackHandler_GetTrackHistory tests GetTrackHistory handler
func TestTrackHandler_GetTrackHistory(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.GET("/tracks/:id/history", handler.GetTrackHistory)

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

	// GetTrackHistory is currently a stub that returns NotImplemented
	assert.Equal(t, http.StatusNotImplemented, w.Code)
}

// TestTrackHandler_RecordPlay tests RecordPlay handler
func TestTrackHandler_RecordPlay_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)

	// Setup playback analytics service
	playbackAnalyticsService := services.NewPlaybackAnalyticsService(db, zaptest.NewLogger(t))
	handler.SetPlaybackAnalyticsService(playbackAnalyticsService)

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

	recordPlayReq := RecordPlayRequest{
		PlayTime: 30,
	}
	body, _ := json.Marshal(recordPlayReq)
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tracks/%s/play", 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_RecordPlay_NotFound tests RecordPlay with non-existent track
func TestTrackHandler_RecordPlay_NotFound(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

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

	// Setup playback analytics service
	playbackAnalyticsService := services.NewPlaybackAnalyticsService(db, zaptest.NewLogger(t))
	handler.SetPlaybackAnalyticsService(playbackAnalyticsService)

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

	nonExistentID := uuid.New()
	recordPlayReq := RecordPlayRequest{
		PlayTime: 30,
	}
	body, _ := json.Marshal(recordPlayReq)
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tracks/%s/play", nonExistentID.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.StatusNotFound, w.Code)
}

// TestTrackHandler_DownloadTrack tests DownloadTrack handler
func TestTrackHandler_DownloadTrack_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)
	track.IsPublic = true
	err = db.Create(track).Error
	require.NoError(t, err)

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

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

	// DownloadTrack may return different status codes depending on file existence
	// At minimum, it should not panic
	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError)
}

// TestTrackHandler_DownloadTrack_NotFound tests DownloadTrack with non-existent track
func TestTrackHandler_DownloadTrack_NotFound(t *testing.T) {
	handler, _, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

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

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

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

// TestTrackHandler_CreateShare tests CreateShare handler
func TestTrackHandler_CreateShare_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)

	// Setup share service
	shareService := services.NewTrackShareService(db)
	handler.SetShareService(shareService)

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

	createShareReq := map[string]interface{}{
		"permissions": "read",
		"expires_at":  time.Now().Add(24 * time.Hour).Format(time.RFC3339),
	}
	body, _ := json.Marshal(createShareReq)
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tracks/%s/share", 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_CreateShare_NotFound tests CreateShare with non-existent track
func TestTrackHandler_CreateShare_NotFound(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

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

	// Setup share service
	shareService := services.NewTrackShareService(db)
	handler.SetShareService(shareService)

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

	nonExistentID := uuid.New()
	createShareReq := map[string]interface{}{
		"permissions": "read",
	}
	body, _ := json.Marshal(createShareReq)
	req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tracks/%s/share", nonExistentID.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.StatusNotFound, w.Code)
}

// TestTrackHandler_GetSharedTrack tests GetSharedTrack handler
func TestTrackHandler_GetSharedTrack_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)

	// Setup share service and create a share
	shareService := services.NewTrackShareService(db)
	handler.SetShareService(shareService)

	shareToken := uuid.New().String()
	expiresAt := time.Now().Add(24 * time.Hour)
	share := &models.TrackShare{
		ID:          uuid.New(),
		TrackID:     trackID,
		ShareToken:  shareToken,
		UserID:      userID,
		Permissions: "read",
		CreatedAt:   time.Now(),
		ExpiresAt:   &expiresAt,
	}
	err = db.Create(share).Error
	require.NoError(t, err)

	router.GET("/tracks/shared/:token", handler.GetSharedTrack)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/tracks/shared/%s", shareToken), 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_GetSharedTrack_InvalidToken tests GetSharedTrack with invalid token
func TestTrackHandler_GetSharedTrack_InvalidToken(t *testing.T) {
	handler, _, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	router.GET("/tracks/shared/:token", handler.GetSharedTrack)

	invalidToken := "invalid-token"
	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/tracks/shared/%s", invalidToken), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestTrackHandler_RevokeShare tests RevokeShare handler
func TestTrackHandler_RevokeShare_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)

	// Setup share service and create a share
	shareService := services.NewTrackShareService(db)
	handler.SetShareService(shareService)

	shareID := uuid.New()
	expiresAt := time.Now().Add(24 * time.Hour)
	share := &models.TrackShare{
		ID:          shareID,
		TrackID:     trackID,
		ShareToken:  uuid.New().String(),
		UserID:      userID,
		Permissions: "read",
		CreatedAt:   time.Now(),
		ExpiresAt:   &expiresAt,
	}
	err = db.Create(share).Error
	require.NoError(t, err)

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

	req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/tracks/share/%s", shareID.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_GetUserLikedTracks tests GetUserLikedTracks handler
func TestTrackHandler_GetUserLikedTracks_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test user
	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 and likes
	track1 := createTestTrack(uuid.New(), userID)
	err = db.Create(track1).Error
	require.NoError(t, err)

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

	like1 := &models.TrackLike{
		ID:      uuid.New(),
		TrackID: track1.ID,
		UserID:  userID,
	}
	err = db.Create(like1).Error
	require.NoError(t, err)

	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})
	router.GET("/users/me/liked", handler.GetUserLikedTracks)

	req := httptest.NewRequest(http.MethodGet, "/users/me/liked?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))
}

// TestTrackHandler_GetTrackLikes tests GetTrackLikes handler
func TestTrackHandler_GetTrackLikes_Success(t *testing.T) {
	handler, db, router, cleanup := setupTestTrackHandler(t)
	defer cleanup()

	// Create test users and track
	userID1 := uuid.New()
	user1 := &models.User{
		ID:       userID1,
		Username: "testuser1",
		Email:    "test1@example.com",
	}
	err := db.Create(user1).Error
	require.NoError(t, err)

	userID2 := uuid.New()
	user2 := &models.User{
		ID:       userID2,
		Username: "testuser2",
		Email:    "test2@example.com",
	}
	err = db.Create(user2).Error
	require.NoError(t, err)

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

	// Create likes
	like1 := &models.TrackLike{
		ID:      uuid.New(),
		TrackID: trackID,
		UserID:  userID1,
	}
	err = db.Create(like1).Error
	require.NoError(t, err)

	like2 := &models.TrackLike{
		ID:      uuid.New(),
		TrackID: trackID,
		UserID:  userID2,
	}
	err = db.Create(like2).Error
	require.NoError(t, err)

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

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/tracks/%s/likes?page=1&limit=10", 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_UnlikeTrack tests UnlikeTrack handler
func TestTrackHandler_UnlikeTrack_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)

	// Create a like first
	like := &models.TrackLike{
		ID:      uuid.New(),
		TrackID: trackID,
		UserID:  userID,
	}
	err = db.Create(like).Error
	require.NoError(t, err)

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

	req := httptest.NewRequest(http.MethodDelete, 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)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.True(t, response["success"].(bool))
}
