package models

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

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

	// Enable foreign keys for SQLite
	db.Exec("PRAGMA foreign_keys = ON")

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

	t.Run("Create TrackShare with all fields", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser",
			Email:        "test@example.com",
			PasswordHash: "hash",
			Slug:         "testuser",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Create track share
		expiresAt := time.Now().Add(24 * time.Hour)
		trackShare := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "test-token-123",
			Permissions: "read,download",
			ExpiresAt:   &expiresAt,
			AccessCount: 0,
		}
		err = db.Create(trackShare).Error
		require.NoError(t, err)

		assert.NotZero(t, trackShare.ID)
		assert.Equal(t, track.ID, trackShare.TrackID)
		assert.Equal(t, user.ID, trackShare.UserID)
		assert.Equal(t, "test-token-123", trackShare.ShareToken)
		assert.Equal(t, "read,download", trackShare.Permissions)
		assert.NotNil(t, trackShare.ExpiresAt)
		assert.Equal(t, int64(0), trackShare.AccessCount)
		assert.False(t, trackShare.CreatedAt.IsZero())
		assert.False(t, trackShare.UpdatedAt.IsZero())
	})

	t.Run("Create TrackShare without expiration", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser2",
			Email:        "test2@example.com",
			PasswordHash: "hash",
			Slug:         "testuser2",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Create track share without expiration
		trackShare := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "test-token-456",
			Permissions: "read",
			ExpiresAt:   nil,
			AccessCount: 0,
		}
		err = db.Create(trackShare).Error
		require.NoError(t, err)

		assert.NotZero(t, trackShare.ID)
		assert.Nil(t, trackShare.ExpiresAt)
		assert.Equal(t, "read", trackShare.Permissions)
	})

	t.Run("TrackShare with unique share_token constraint", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser3",
			Email:        "test3@example.com",
			PasswordHash: "hash",
			Slug:         "testuser3",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Create first track share
		trackShare1 := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "unique-token-123",
			Permissions: "read",
		}
		err = db.Create(trackShare1).Error
		require.NoError(t, err)

		// Try to create second track share with same token
		trackShare2 := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "unique-token-123",
			Permissions: "read",
		}
		err = db.Create(trackShare2).Error
		assert.Error(t, err) // Should fail due to unique constraint
	})

	t.Run("TrackShare cascade delete on track deletion", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser4",
			Email:        "test4@example.com",
			PasswordHash: "hash",
			Slug:         "testuser4",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Create track share
		trackShare := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "cascade-token-123",
			Permissions: "read",
		}
		err = db.Create(trackShare).Error
		require.NoError(t, err)

		shareID := trackShare.ID

		// Delete track (hard delete)
		err = db.Unscoped().Delete(track).Error
		require.NoError(t, err)

		// Verify track share is also deleted (cascade)
		// Note: SQLite in-memory may not enforce foreign key constraints the same way as PostgreSQL
		// So we check if the share still exists or was soft-deleted
		var deletedShare TrackShare
		err = db.Unscoped().First(&deletedShare, shareID).Error
		// The share should be deleted (either hard or soft delete depending on DB behavior)
		// In production with PostgreSQL, it will be hard deleted due to CASCADE
		if err == nil {
			// If still exists, verify it's at least soft-deleted
			assert.NotNil(t, deletedShare.DeletedAt)
		} else {
			// If not found, it was hard deleted (expected behavior)
			assert.Equal(t, gorm.ErrRecordNotFound, err)
		}
	})

	t.Run("TrackShare TableName", func(t *testing.T) {
		share := &TrackShare{}
		assert.Equal(t, "track_shares", share.TableName())
	})

	t.Run("TrackShare with different permissions", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser5",
			Email:        "test5@example.com",
			PasswordHash: "hash",
			Slug:         "testuser5",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Test different permission values
		permissions := []string{"read", "download", "read,download"}

		for i, perm := range permissions {
			trackShare := &TrackShare{
				TrackID:     track.ID,
				UserID:      user.ID,
				ShareToken:  "perm-token-" + string(rune(i)),
				Permissions: perm,
			}
			err = db.Create(trackShare).Error
			require.NoError(t, err)
			assert.Equal(t, perm, trackShare.Permissions)
		}
	})

	t.Run("TrackShare increment access_count", func(t *testing.T) {
		// Create user
		user := &User{
			Username:     "testuser6",
			Email:        "test6@example.com",
			PasswordHash: "hash",
			Slug:         "testuser6",
			IsActive:     true,
		}
		err := db.Create(user).Error
		require.NoError(t, err)

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

		// Create track share
		trackShare := &TrackShare{
			TrackID:     track.ID,
			UserID:      user.ID,
			ShareToken:  "access-token-123",
			Permissions: "read",
			AccessCount: 0,
		}
		err = db.Create(trackShare).Error
		require.NoError(t, err)

		// Increment access count
		trackShare.AccessCount++
		err = db.Save(trackShare).Error
		require.NoError(t, err)

		// Verify access count was incremented
		var updatedShare TrackShare
		err = db.First(&updatedShare, trackShare.ID).Error
		require.NoError(t, err)
		assert.Equal(t, int64(1), updatedShare.AccessCount)
	})
}
