package handlers

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"veza-backend-api/internal/models"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockPlaylistServiceForExport implements PlaylistExportServiceInterface
type MockPlaylistServiceForExport struct {
	mock.Mock
}

func (m *MockPlaylistServiceForExport) GetPlaylist(ctx context.Context, playlistID uuid.UUID, userID *uuid.UUID) (*models.Playlist, error) {
	args := m.Called(ctx, playlistID, userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.Playlist), args.Error(1)
}

func (m *MockPlaylistServiceForExport) CheckPermission(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, requiredPermission models.PlaylistPermission) (bool, error) {
	args := m.Called(ctx, playlistID, userID, requiredPermission)
	return args.Bool(0), args.Error(1)
}

func setupTestPlaylistExportHandler(t *testing.T) (*PlaylistExportHandler, *MockPlaylistServiceForExport) {
	mockService := new(MockPlaylistServiceForExport)
	handler := NewPlaylistExportHandlerWithInterface(mockService)
	return handler, mockService
}

func TestExportPlaylistJSON_Success(t *testing.T) {
	handler, mockService := setupTestPlaylistExportHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	playlistID := uuid.New()
	userID := uuid.New()
	c.Params = []gin.Param{{Key: "id", Value: playlistID.String()}}
	c.Set("user_id", userID)

	req, _ := http.NewRequest("GET", "/playlists/"+playlistID.String()+"/export/json", nil)
	c.Request = req

	expectedPlaylist := &models.Playlist{
		ID:       playlistID,
		UserID:   userID,
		Title:    "Test Playlist",
		IsPublic: false,
		Tracks: []models.PlaylistTrack{
			{
				Position: 1,
				Track: models.Track{
					ID:    uuid.New(),
					Title: "Track 1",
				},
				AddedAt: time.Now(),
			},
		},
	}

	mockService.On("GetPlaylist", mock.Anything, playlistID, &userID).Return(expectedPlaylist, nil)

	handler.ExportPlaylistJSON(c)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
	assert.Contains(t, w.Body.String(), "Test Playlist")
	assert.Contains(t, w.Body.String(), "Track 1")

	mockService.AssertExpectations(t)
}

func TestExportPlaylistCSV_Success(t *testing.T) {
	handler, mockService := setupTestPlaylistExportHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	playlistID := uuid.New()
	userID := uuid.New()
	c.Params = []gin.Param{{Key: "id", Value: playlistID.String()}}
	c.Set("user_id", userID)

	req, _ := http.NewRequest("GET", "/playlists/"+playlistID.String()+"/export/csv", nil)
	c.Request = req

	expectedPlaylist := &models.Playlist{
		ID:       playlistID,
		UserID:   userID,
		Title:    "Test Playlist",
		IsPublic: false,
		Tracks: []models.PlaylistTrack{
			{
				Position: 1,
				Track: models.Track{
					ID:     uuid.New(),
					Title:  "Track 1",
					Artist: "Artist 1",
				},
				AddedAt: time.Now(),
			},
		},
	}

	mockService.On("GetPlaylist", mock.Anything, playlistID, &userID).Return(expectedPlaylist, nil)

	handler.ExportPlaylistCSV(c)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "text/csv", w.Header().Get("Content-Type"))
	assert.Contains(t, w.Body.String(), "Track 1")
	assert.Contains(t, w.Body.String(), "Artist 1")

	mockService.AssertExpectations(t)
}

func TestExportPlaylistJSON_NotFound(t *testing.T) {
	handler, mockService := setupTestPlaylistExportHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	playlistID := uuid.New()
	userID := uuid.New()
	c.Params = []gin.Param{{Key: "id", Value: playlistID.String()}}
	c.Set("user_id", userID)

	req, _ := http.NewRequest("GET", "/playlists/"+playlistID.String()+"/export/json", nil)
	c.Request = req

	mockService.On("GetPlaylist", mock.Anything, playlistID, &userID).Return(nil, errors.New("playlist not found"))

	handler.ExportPlaylistJSON(c)

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

func TestExportPlaylistJSON_Forbidden(t *testing.T) {
	handler, mockService := setupTestPlaylistExportHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	playlistID := uuid.New()
	userID := uuid.New()
	otherUserID := uuid.New()

	c.Params = []gin.Param{{Key: "id", Value: playlistID.String()}}
	c.Set("user_id", userID)

	req, _ := http.NewRequest("GET", "/playlists/"+playlistID.String()+"/export/json", nil)
	c.Request = req

	expectedPlaylist := &models.Playlist{
		ID:       playlistID,
		UserID:   otherUserID, // Owned by someone else
		IsPublic: false,       // Private
	}

	mockService.On("GetPlaylist", mock.Anything, playlistID, &userID).Return(expectedPlaylist, nil)
	// CheckPermission returns false
	mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(false, nil)

	handler.ExportPlaylistJSON(c)

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