package services

import (
	"context"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

func setupTestBackupService(t *testing.T) (*BackupService, string, func()) {
	tmpDir, err := os.MkdirTemp("", "backup_test_*")
	require.NoError(t, err)

	config := BackupConfig{
		BackupDir:     tmpDir,
		RetentionDays: 7,
		DatabaseName:  "test_db",
		DatabaseHost:  "localhost",
		DatabasePort:  "5432",
		DatabaseUser:  "test_user",
	}

	logger := zap.NewNop()
	service, err := NewBackupService(config, logger)
	require.NoError(t, err)

	cleanup := func() {
		os.RemoveAll(tmpDir)
	}

	return service, tmpDir, cleanup
}

func TestBackupService_NewBackupService_Success(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "backup_test_*")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	config := BackupConfig{
		BackupDir:     tmpDir,
		RetentionDays: 30,
		DatabaseName:  "test_db",
	}

	logger := zap.NewNop()
	service, err := NewBackupService(config, logger)

	assert.NoError(t, err)
	assert.NotNil(t, service)
	assert.Equal(t, tmpDir, service.backupDir)
	assert.Equal(t, 30, service.retentionDays)
	assert.Equal(t, "test_db", service.databaseName)
}

func TestBackupService_NewBackupService_CreatesDirectory(t *testing.T) {
	tmpDir := filepath.Join(os.TempDir(), "backup_test_new_*")
	defer os.RemoveAll(tmpDir)

	config := BackupConfig{
		BackupDir:     tmpDir,
		RetentionDays: 30,
		DatabaseName:  "test_db",
	}

	logger := zap.NewNop()
	service, err := NewBackupService(config, logger)

	assert.NoError(t, err)
	assert.NotNil(t, service)

	// Verify directory was created
	info, err := os.Stat(tmpDir)
	assert.NoError(t, err)
	assert.True(t, info.IsDir())
}

func TestBackupService_NewBackupService_DefaultRetentionDays(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "backup_test_*")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	config := BackupConfig{
		BackupDir:     tmpDir,
		RetentionDays: 0, // Should default to 30
		DatabaseName:  "test_db",
	}

	logger := zap.NewNop()
	service, err := NewBackupService(config, logger)

	assert.NoError(t, err)
	assert.NotNil(t, service)
	assert.Equal(t, 30, service.retentionDays)
}

func TestBackupService_NewBackupService_NegativeRetentionDays(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "backup_test_*")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	config := BackupConfig{
		BackupDir:     tmpDir,
		RetentionDays: -5, // Should default to 30
		DatabaseName:  "test_db",
	}

	logger := zap.NewNop()
	service, err := NewBackupService(config, logger)

	assert.NoError(t, err)
	assert.NotNil(t, service)
	assert.Equal(t, 30, service.retentionDays)
}

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

	ctx := context.Background()
	err := service.CleanupOldBackups(ctx)

	assert.NoError(t, err)
}

func TestBackupService_CleanupOldBackups_DeletesOldBackups(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create old backup file (older than retention period)
	oldTime := time.Now().AddDate(0, 0, -10) // 10 days ago
	oldBackupPath := filepath.Join(backupDir, "test_db_20200101_000000.sql")
	oldFile, err := os.Create(oldBackupPath)
	require.NoError(t, err)
	oldFile.Close()

	// Set modification time to old time
	err = os.Chtimes(oldBackupPath, oldTime, oldTime)
	require.NoError(t, err)

	// Create recent backup file (within retention period)
	recentBackupPath := filepath.Join(backupDir, "test_db_20231228_120000.sql")
	recentFile, err := os.Create(recentBackupPath)
	require.NoError(t, err)
	recentFile.Close()

	// Run cleanup
	err = service.CleanupOldBackups(ctx)
	assert.NoError(t, err)

	// Verify old backup was deleted
	_, err = os.Stat(oldBackupPath)
	assert.True(t, os.IsNotExist(err))

	// Verify recent backup still exists
	_, err = os.Stat(recentBackupPath)
	assert.NoError(t, err)
}

func TestBackupService_CleanupOldBackups_IgnoresNonBackupFiles(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create a non-backup file
	otherFile := filepath.Join(backupDir, "other_file.txt")
	err := os.WriteFile(otherFile, []byte("test"), 0644)
	require.NoError(t, err)

	// Run cleanup
	err = service.CleanupOldBackups(ctx)
	assert.NoError(t, err)

	// Verify non-backup file still exists
	_, err = os.Stat(otherFile)
	assert.NoError(t, err)
}

func TestBackupService_CleanupOldBackups_IgnoresDirectories(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create a subdirectory
	subDir := filepath.Join(backupDir, "subdir")
	err := os.MkdirAll(subDir, 0755)
	require.NoError(t, err)

	// Run cleanup
	err = service.CleanupOldBackups(ctx)
	assert.NoError(t, err)

	// Verify subdirectory still exists
	_, err = os.Stat(subDir)
	assert.NoError(t, err)
}

func TestBackupService_CleanupOldBackups_HandlesDumpFiles(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create old .dump file
	oldTime := time.Now().AddDate(0, 0, -10)
	oldBackupPath := filepath.Join(backupDir, "test_db_20200101_000000.dump")
	oldFile, err := os.Create(oldBackupPath)
	require.NoError(t, err)
	oldFile.Close()

	err = os.Chtimes(oldBackupPath, oldTime, oldTime)
	require.NoError(t, err)

	// Run cleanup
	err = service.CleanupOldBackups(ctx)
	assert.NoError(t, err)

	// Verify old .dump file was deleted
	_, err = os.Stat(oldBackupPath)
	assert.True(t, os.IsNotExist(err))
}

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

	ctx := context.Background()
	backups, err := service.ListBackups(ctx)

	assert.NoError(t, err)
	if backups == nil {
		backups = []BackupInfo{} // Initialize empty slice if nil
	}
	assert.Len(t, backups, 0)
}

func TestBackupService_ListBackups_WithBackups(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create backup files
	backup1 := filepath.Join(backupDir, "test_db_20231228_120000.sql")
	err := os.WriteFile(backup1, []byte("backup1"), 0644)
	require.NoError(t, err)

	backup2 := filepath.Join(backupDir, "test_db_20231228_130000.dump")
	err = os.WriteFile(backup2, []byte("backup2"), 0644)
	require.NoError(t, err)

	// Create non-backup file (should be ignored)
	otherFile := filepath.Join(backupDir, "other.txt")
	err = os.WriteFile(otherFile, []byte("other"), 0644)
	require.NoError(t, err)

	// List backups
	backups, err := service.ListBackups(ctx)

	assert.NoError(t, err)
	assert.Len(t, backups, 2)

	// Verify backup files are listed
	backupNames := make(map[string]bool)
	for _, backup := range backups {
		backupNames[backup.FileName] = true
		assert.NotEmpty(t, backup.FilePath)
		assert.Greater(t, backup.Size, int64(0))
		assert.NotZero(t, backup.CreatedAt)
	}

	assert.True(t, backupNames["test_db_20231228_120000.sql"])
	assert.True(t, backupNames["test_db_20231228_130000.dump"])
}

func TestBackupService_ListBackups_IgnoresDirectories(t *testing.T) {
	service, backupDir, cleanup := setupTestBackupService(t)
	defer cleanup()

	ctx := context.Background()

	// Create subdirectory
	subDir := filepath.Join(backupDir, "subdir")
	err := os.MkdirAll(subDir, 0755)
	require.NoError(t, err)

	// Create backup file
	backup1 := filepath.Join(backupDir, "test_db_20231228_120000.sql")
	err = os.WriteFile(backup1, []byte("backup1"), 0644)
	require.NoError(t, err)

	// List backups
	backups, err := service.ListBackups(ctx)

	assert.NoError(t, err)
	assert.Len(t, backups, 1)
	assert.Equal(t, "test_db_20231228_120000.sql", backups[0].FileName)
}

func TestBackupService_CreateBackup_RequiresPostgreSQL(t *testing.T) {
	// Skip this test as it requires PostgreSQL and pg_dump
	t.Skip("Skipping test that requires PostgreSQL pg_dump command")
}

func TestBackupService_BackupResult_Fields(t *testing.T) {
	result := &BackupResult{
		Success:      true,
		BackupPath:   "/path/to/backup.sql",
		BackupSize:   1024,
		Duration:     5 * time.Second,
		ErrorMessage: "",
		CreatedAt:    time.Now(),
	}

	assert.True(t, result.Success)
	assert.Equal(t, "/path/to/backup.sql", result.BackupPath)
	assert.Equal(t, int64(1024), result.BackupSize)
	assert.Equal(t, 5*time.Second, result.Duration)
	assert.Empty(t, result.ErrorMessage)
	assert.NotZero(t, result.CreatedAt)
}

func TestBackupService_BackupInfo_Fields(t *testing.T) {
	info := BackupInfo{
		FileName:  "test_db_20231228_120000.sql",
		FilePath:  "/path/to/test_db_20231228_120000.sql",
		Size:      2048,
		CreatedAt: time.Now(),
	}

	assert.Equal(t, "test_db_20231228_120000.sql", info.FileName)
	assert.Equal(t, "/path/to/test_db_20231228_120000.sql", info.FilePath)
	assert.Equal(t, int64(2048), info.Size)
	assert.NotZero(t, info.CreatedAt)
}

func TestBackupService_BackupConfig_Fields(t *testing.T) {
	config := BackupConfig{
		BackupDir:     "/backups",
		RetentionDays: 30,
		DatabaseURL:   "postgres://user:pass@localhost/db",
		DatabaseName:  "test_db",
		DatabaseUser:  "test_user",
		DatabaseHost:  "localhost",
		DatabasePort:  "5432",
	}

	assert.Equal(t, "/backups", config.BackupDir)
	assert.Equal(t, 30, config.RetentionDays)
	assert.Equal(t, "postgres://user:pass@localhost/db", config.DatabaseURL)
	assert.Equal(t, "test_db", config.DatabaseName)
	assert.Equal(t, "test_user", config.DatabaseUser)
	assert.Equal(t, "localhost", config.DatabaseHost)
	assert.Equal(t, "5432", config.DatabasePort)
}
