package repositories

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

func TestUserRepository_Create(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	user := &models.User{
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	user.SetPassword("Password123")

	err := repo.Create(user)
	require.NoError(t, err)
	assert.NotZero(t, user.ID)
}

func TestUserRepository_FindByID(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	// Find by ID
	found, err := repo.FindByID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.ID)
	assert.Equal(t, "testuser", found.Username)
	assert.Equal(t, "test@example.com", found.Email)
}

func TestUserRepository_FindByID_NotFound(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	_, err := repo.FindByID(9999)
	assert.Error(t, err)
}

func TestUserRepository_FindByUsername(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	// Find by username
	found, err := repo.FindByUsername("testuser")
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.ID)
}

func TestUserRepository_FindByEmail(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	// Find by email
	found, err := repo.FindByEmail("test@example.com")
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.ID)
}

func TestUserRepository_FindByUsernameOrEmail(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	tests := []struct {
		name     string
		input    string
		expected uint
	}{
		{"find by username", "testuser", user.ID},
		{"find by email", "test@example.com", user.ID},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			found, err := repo.FindByUsernameOrEmail(tt.input)
			require.NoError(t, err)
			assert.Equal(t, tt.expected, found.ID)
		})
	}
}

func TestUserRepository_Update(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	// Update user
	user.FirstName = "John"
	user.LastName = "Doe"
	err := repo.Update(user)
	require.NoError(t, err)

	// Verify update
	found, err := repo.FindByID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, "John", found.FirstName)
	assert.Equal(t, "Doe", found.LastName)
}

func TestUserRepository_ExistsByUsername(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")

	tests := []struct {
		name     string
		username string
		expected bool
	}{
		{"existing user", "existinguser", true},
		{"non-existing user", "nonexistent", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			exists, err := repo.ExistsByUsername(tt.username)
			require.NoError(t, err)
			assert.Equal(t, tt.expected, exists)
		})
	}
}

func TestUserRepository_ExistsByEmail(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")

	tests := []struct {
		name     string
		email    string
		expected bool
	}{
		{"existing email", "existing@example.com", true},
		{"non-existing email", "nonexistent@example.com", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			exists, err := repo.ExistsByEmail(tt.email)
			require.NoError(t, err)
			assert.Equal(t, tt.expected, exists)
		})
	}
}

func TestUserRepository_GetOrCreateProfile(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	// Create user
	user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")

	// First call should create
	profile1, err := repo.GetOrCreateProfile(user.ID)
	require.NoError(t, err)
	assert.NotZero(t, profile1.ID)

	// Second call should return same profile
	profile2, err := repo.GetOrCreateProfile(user.ID)
	require.NoError(t, err)
	assert.Equal(t, profile1.ID, profile2.ID)
}

func TestUserRepository_FindAuthProvider(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewUserRepository(db)

	t.Run("email user", func(t *testing.T) {
		user := testutil.CreateTestUser(t, db, "emailuser", "email@test.com", "Password123")
		provider, err := repo.FindAuthProvider(user.ID)
		require.NoError(t, err)
		assert.Equal(t, "email", provider)
	})

	t.Run("apple user", func(t *testing.T) {
		user := testutil.CreateTestUser(t, db, "appleuser", "apple@test.com", "Password123")
		appleAuth := &models.AppleSocialAuth{
			UserID:  user.ID,
			AppleID: "apple_sub_test",
			Email:   "apple@test.com",
		}
		require.NoError(t, db.Create(appleAuth).Error)

		provider, err := repo.FindAuthProvider(user.ID)
		require.NoError(t, err)
		assert.Equal(t, "apple", provider)
	})

	t.Run("google user", func(t *testing.T) {
		user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "Password123")
		googleAuth := &models.GoogleSocialAuth{
			UserID:   user.ID,
			GoogleID: "google_sub_test",
			Email:    "google@test.com",
		}
		require.NoError(t, db.Create(googleAuth).Error)

		provider, err := repo.FindAuthProvider(user.ID)
		require.NoError(t, err)
		assert.Equal(t, "google", provider)
	})
}

func TestUserRepository_DeleteUserCascade(t *testing.T) {
	t.Run("deletes user with no residences", func(t *testing.T) {
		db := testutil.SetupTestDB(t)
		repo := NewUserRepository(db)

		user := testutil.CreateTestUser(t, db, "deletebare", "deletebare@test.com", "Password123")

		// Create profile and token
		profile := &models.UserProfile{UserID: user.ID, Verified: true}
		require.NoError(t, db.Create(profile).Error)
		_, err := models.GetOrCreateToken(db, user.ID)
		require.NoError(t, err)

		var fileURLs []string
		txErr := repo.Transaction(func(txRepo *UserRepository) error {
			urls, err := txRepo.DeleteUserCascade(user.ID)
			if err != nil {
				return err
			}
			fileURLs = urls
			return nil
		})
		require.NoError(t, txErr)
		assert.Empty(t, fileURLs)

		// Verify user is gone
		var count int64
		db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count)
		assert.Equal(t, int64(0), count)

		// Verify profile is gone
		db.Model(&models.UserProfile{}).Where("user_id = ?", user.ID).Count(&count)
		assert.Equal(t, int64(0), count)

		// Verify token is gone
		db.Model(&models.AuthToken{}).Where("user_id = ?", user.ID).Count(&count)
		assert.Equal(t, int64(0), count)
	})

	t.Run("returns file URLs for cleanup", func(t *testing.T) {
		db := testutil.SetupTestDB(t)
		repo := NewUserRepository(db)

		user := testutil.CreateTestUser(t, db, "deletefiles", "deletefiles@test.com", "Password123")
		residence := testutil.CreateTestResidence(t, db, user.ID, "Test Home")

		// Create document with file
		doc := &models.Document{
			ResidenceID: residence.ID,
			CreatedByID: user.ID,
			Title:       "Test Doc",
			FileURL:     "/uploads/documents/test.pdf",
		}
		require.NoError(t, db.Create(doc).Error)

		// Create document image
		docImage := &models.DocumentImage{
			DocumentID: doc.ID,
			ImageURL:   "/uploads/images/docimg.jpg",
		}
		require.NoError(t, db.Create(docImage).Error)

		var fileURLs []string
		txErr := repo.Transaction(func(txRepo *UserRepository) error {
			urls, err := txRepo.DeleteUserCascade(user.ID)
			if err != nil {
				return err
			}
			fileURLs = urls
			return nil
		})
		require.NoError(t, txErr)

		// Should return the file URLs
		assert.Contains(t, fileURLs, "/uploads/documents/test.pdf")
		assert.Contains(t, fileURLs, "/uploads/images/docimg.jpg")

		// Verify everything deleted
		var count int64
		db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count)
		assert.Equal(t, int64(0), count)
		db.Model(&models.Residence{}).Where("id = ?", residence.ID).Count(&count)
		assert.Equal(t, int64(0), count)
		db.Model(&models.Document{}).Where("id = ?", doc.ID).Count(&count)
		assert.Equal(t, int64(0), count)
	})

	t.Run("handles user with owned and shared residences", func(t *testing.T) {
		db := testutil.SetupTestDB(t)
		repo := NewUserRepository(db)

		owner := testutil.CreateTestUser(t, db, "deleteowner", "deleteowner@test.com", "Password123")
		otherUser := testutil.CreateTestUser(t, db, "otheruser", "other@test.com", "Password123")
		otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other Home")

		// Owner's residence
		ownedResidence := testutil.CreateTestResidence(t, db, owner.ID, "Owner Home")

		// Add owner as member of other user's residence
		db.Exec("INSERT INTO residence_residence_users (residence_id, user_id) VALUES (?, ?)", otherResidence.ID, owner.ID)

		txErr := repo.Transaction(func(txRepo *UserRepository) error {
			_, err := txRepo.DeleteUserCascade(owner.ID)
			return err
		})
		require.NoError(t, txErr)

		// Owner's residence should be deleted
		var count int64
		db.Model(&models.Residence{}).Where("id = ?", ownedResidence.ID).Count(&count)
		assert.Equal(t, int64(0), count)

		// Other user's residence should still exist
		db.Model(&models.Residence{}).Where("id = ?", otherResidence.ID).Count(&count)
		assert.Equal(t, int64(1), count)

		// Owner should no longer be a member of other's residence
		db.Raw("SELECT COUNT(*) FROM residence_residence_users WHERE user_id = ?", owner.ID).Count(&count)
		assert.Equal(t, int64(0), count)

		// Other user should still exist
		db.Model(&models.User{}).Where("id = ?", otherUser.ID).Count(&count)
		assert.Equal(t, int64(1), count)
	})
}
