package services

import (
	"context"
	"errors"
	"github.com/google/uuid"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
)

func setupTestTrackShareService(t *testing.T) (*TrackShareService, *gorm.DB, uuid.UUID, func()) {
	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Auto-migrate
	err = db.AutoMigrate(&models.TrackShare{}, &models.Track{}, &models.User{})
	require.NoError(t, err)

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

	// Setup service
	// TrackShareService might need logger too?
	// The original test didn't pass one, assuming NewTrackShareService(db) only.
	// Checking the file content, it was: NewTrackShareService(db)
	service := NewTrackShareService(db)

	// Cleanup function
	cleanup := func() {
		// Database will be closed automatically
	}

	return service, db, userID, cleanup
}

func TestTrackShareService_CreateShare(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Create share
	share, err := service.CreateShare(ctx, track.ID, userID, "read,download", nil)
	assert.NoError(t, err)
	assert.NotNil(t, share)
	assert.Equal(t, track.ID, share.TrackID)
	assert.Equal(t, userID, share.UserID)
	assert.Equal(t, "read,download", share.Permissions)
	assert.NotEmpty(t, share.ShareToken)
}

func TestTrackShareService_CreateShare_NotOwner(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Try to create share as different user
	share, err := service.CreateShare(ctx, track.ID, uuid.New(), "read,download", nil)
	assert.Error(t, err)
	assert.Nil(t, share)
	assert.Equal(t, ErrForbidden, err)
}

func TestTrackShareService_ValidateShareToken(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Create share
	share, err := service.CreateShare(ctx, track.ID, userID, "read,download", nil)
	require.NoError(t, err)

	// Validate token
	validatedShare, err := service.ValidateShareToken(ctx, share.ShareToken)
	assert.NoError(t, err)
	assert.NotNil(t, validatedShare)
	assert.Equal(t, share.ID, validatedShare.ID)
	assert.Equal(t, int64(1), validatedShare.AccessCount) // Should be incremented
}

func TestTrackShareService_ValidateShareToken_Expired(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Create share with expiration in the past
	expiredTime := time.Now().Add(-1 * time.Hour)
	share := &models.TrackShare{
		TrackID:     track.ID,
		UserID:      userID,
		ShareToken:  "test-token-123",
		Permissions: "read,download",
		ExpiresAt:   &expiredTime,
		AccessCount: 0,
	}
	err = db.Create(share).Error
	require.NoError(t, err)

	// Try to validate expired token
	validatedShare, err := service.ValidateShareToken(ctx, share.ShareToken)
	assert.Error(t, err)
	assert.Nil(t, validatedShare)
	assert.Equal(t, ErrShareExpired, err)
}

func TestTrackShareService_CheckPermission(t *testing.T) {
	service, _, _, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	// Test with read permission
	share := &models.TrackShare{
		Permissions: "read",
		ExpiresAt:   nil,
	}
	assert.True(t, service.CheckPermission(share, "read"))
	assert.False(t, service.CheckPermission(share, "download"))

	// Test with download permission
	share.Permissions = "download"
	assert.False(t, service.CheckPermission(share, "read"))
	assert.True(t, service.CheckPermission(share, "download"))

	// Test with both permissions
	share.Permissions = "read,download"
	assert.True(t, service.CheckPermission(share, "read"))
	assert.True(t, service.CheckPermission(share, "download"))

	// Test with expired share
	expiredTime := time.Now().Add(-1 * time.Hour)
	share.ExpiresAt = &expiredTime
	assert.False(t, service.CheckPermission(share, "read"))
	assert.False(t, service.CheckPermission(share, "download"))
}

func TestTrackShareService_RevokeShare(t *testing.T) {
	service, db, userID, cleanup := setupTestTrackShareService(t)
	defer cleanup()

	ctx := context.Background()

	// Create test track
	track := &models.Track{
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test/track.mp3",
		FileSize: 5 * 1024 * 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	err := db.Create(track).Error
	require.NoError(t, err)

	// Create share
	share, err := service.CreateShare(ctx, track.ID, userID, "read,download", nil)
	require.NoError(t, err)

	// Revoke share
	err = service.RevokeShare(ctx, share.ID, userID)
	assert.NoError(t, err)

	// Verify share is deleted
	var deletedShare models.TrackShare
	err = db.First(&deletedShare, "id = ?", share.ID).Error
	assert.Error(t, err)
	assert.True(t, errors.Is(err, gorm.ErrRecordNotFound))
}
