package models

import (
	"testing"
	"time"

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

func setupTestTrackVersionDB(t *testing.T) (*gorm.DB, func()) {
	// Setup in-memory SQLite database
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")
	sqlDB, _ := db.DB()
	sqlDB.SetMaxOpenConns(1)

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

	// Cleanup function
	cleanup := func() {
		// SQLite in-memory database doesn't need explicit cleanup
	}

	return db, cleanup
}

func TestTrackVersion_Create(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create track version
	version := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version).Error
	require.NoError(t, err)

	// Verify version was created
	assert.NotEqual(t, uuid.Nil, version.ID)
	assert.Equal(t, track.ID, version.TrackID)
	assert.Equal(t, 1, version.VersionNumber)
	assert.Equal(t, "/path/to/track_v1.mp3", version.FilePath)
	assert.Equal(t, "Initial version", version.Changelog)
	assert.False(t, version.CreatedAt.IsZero())
	assert.False(t, version.UpdatedAt.IsZero())
}

func TestTrackVersion_WithTrack(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create track version
	version := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version).Error
	require.NoError(t, err)

	// Load version with track relation
	var versionWithTrack TrackVersion
	err = db.Preload("Track").First(&versionWithTrack, version.ID).Error
	require.NoError(t, err)

	assert.NotNil(t, versionWithTrack.Track)
	assert.Equal(t, track.ID, versionWithTrack.Track.ID)
	assert.Equal(t, "Test Track", versionWithTrack.Track.Title)
}

func TestTrackVersion_MultipleVersions(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create multiple versions
	version1 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	version2 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 2,
		FilePath:      "/path/to/track_v2.mp3",
		FileSize:      2048,
		Changelog:     "Updated mix",
	}
	version3 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 3,
		FilePath:      "/path/to/track_v3.mp3",
		FileSize:      3072,
		Changelog:     "Final version",
	}

	err = db.Create(version1).Error
	require.NoError(t, err)
	err = db.Create(version2).Error
	require.NoError(t, err)
	err = db.Create(version3).Error
	require.NoError(t, err)

	// Load all versions for the track
	var versions []TrackVersion
	err = db.Where("track_id = ?", track.ID).Order("version_number ASC").Find(&versions).Error
	require.NoError(t, err)

	assert.Equal(t, 3, len(versions))
	assert.Equal(t, 1, versions[0].VersionNumber)
	assert.Equal(t, 2, versions[1].VersionNumber)
	assert.Equal(t, 3, versions[2].VersionNumber)
}

func TestTrackVersion_CascadeDeleteOnTrack(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create track version
	version := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version).Error
	require.NoError(t, err)

	versionID := version.ID

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

	// Verify version is deleted (cascade)
	var deletedVersion TrackVersion
	err = db.First(&deletedVersion, versionID).Error
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)
}

func TestTrackVersion_UniqueVersionNumber(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create first version
	version1 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version1).Error
	require.NoError(t, err)

	// Try to create another version with the same version number
	version2 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1, // Same version number
		FilePath:      "/path/to/track_v1_dup.mp3",
		FileSize:      1024,
		Changelog:     "Duplicate version",
	}
	err = db.Create(version2).Error
	// Should fail due to unique constraint
	assert.Error(t, err)
}

func TestTrackVersion_TableName(t *testing.T) {
	version := TrackVersion{}
	assert.Equal(t, "track_versions", version.TableName())
}

func TestTrackVersion_Timestamps(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create version
	now := time.Now()
	version := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version).Error
	require.NoError(t, err)

	// Verify timestamps are set
	assert.True(t, version.CreatedAt.After(now.Add(-time.Second)))
	assert.True(t, version.CreatedAt.Before(now.Add(time.Second)))
	assert.True(t, version.UpdatedAt.After(now.Add(-time.Second)))
	assert.True(t, version.UpdatedAt.Before(now.Add(time.Second)))
}

func TestTrackVersion_SoftDelete(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create version
	version := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	err = db.Create(version).Error
	require.NoError(t, err)

	versionID := version.ID

	// Soft delete version
	err = db.Delete(version).Error
	require.NoError(t, err)

	// Verify version is soft deleted (not found in normal query)
	var deletedVersion TrackVersion
	err = db.First(&deletedVersion, versionID).Error
	assert.Error(t, err)
	assert.Equal(t, gorm.ErrRecordNotFound, err)

	// Verify version exists with Unscoped
	var unscopedVersion TrackVersion
	err = db.Unscoped().First(&unscopedVersion, versionID).Error
	require.NoError(t, err)
	assert.NotNil(t, unscopedVersion.DeletedAt)
}

func TestTrackVersion_Relations(t *testing.T) {
	db, cleanup := setupTestTrackVersionDB(t)
	defer cleanup()

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

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

	// Create versions
	version1 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 1,
		FilePath:      "/path/to/track_v1.mp3",
		FileSize:      1024,
		Changelog:     "Initial version",
	}
	version2 := &TrackVersion{
		TrackID:       track.ID,
		VersionNumber: 2,
		FilePath:      "/path/to/track_v2.mp3",
		FileSize:      2048,
		Changelog:     "Updated version",
	}
	err = db.Create(version1).Error
	require.NoError(t, err)
	err = db.Create(version2).Error
	require.NoError(t, err)

	// Load track with versions
	var trackWithVersions Track
	err = db.Preload("Versions").First(&trackWithVersions, track.ID).Error
	require.NoError(t, err)

	assert.Equal(t, 2, len(trackWithVersions.Versions))
}
