package backup

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"time"

	"git.haelnorr.com/h/oslstats/internal/config"
	"github.com/pkg/errors"
)

// CreateBackup creates a compressed PostgreSQL dump before migrations
// Returns backup filename and error
// If pg_dump is not available, returns nil error with warning
func CreateBackup(ctx context.Context, cfg *config.Config, operation string) (string, error) {
	// Check if pg_dump is available
	if _, err := exec.LookPath("pg_dump"); err != nil {
		fmt.Println("[WARN] pg_dump not found - skipping backup")
		fmt.Println("[WARN] Install PostgreSQL client tools for automatic backups:")
		fmt.Println("[WARN]   Ubuntu/Debian: sudo apt-get install postgresql-client")
		fmt.Println("[WARN]   macOS: brew install postgresql")
		fmt.Println("[WARN]   Arch: sudo pacman -S postgresql-libs")
		return "", nil // Don't fail, just warn
	}

	// Ensure backup directory exists
	if err := os.MkdirAll(cfg.DB.BackupDir, 0755); err != nil {
		return "", errors.Wrap(err, "failed to create backup directory")
	}

	// Generate filename: YYYYMMDD_HHmmss_pre_{operation}.sql.gz
	timestamp := time.Now().Format("20060102_150405")
	filename := filepath.Join(cfg.DB.BackupDir,
		fmt.Sprintf("%s_pre_%s.sql.gz", timestamp, operation))

	// Check if gzip is available
	useGzip := true
	if _, err := exec.LookPath("gzip"); err != nil {
		fmt.Println("[WARN] gzip not found - using uncompressed backup")
		useGzip = false
		filename = filepath.Join(cfg.DB.BackupDir,
			fmt.Sprintf("%s_pre_%s.sql", timestamp, operation))
	}

	// Build pg_dump command
	var cmd *exec.Cmd
	if useGzip {
		// Use shell to pipe pg_dump through gzip
		pgDumpCmd := fmt.Sprintf(
			"pg_dump -h %s -p %d -U %s -d %s --no-owner --no-acl --clean --if-exists | gzip > %s",
			cfg.DB.Host,
			cfg.DB.Port,
			cfg.DB.User,
			cfg.DB.DB,
			filename,
		)
		cmd = exec.CommandContext(ctx, "sh", "-c", pgDumpCmd)
	} else {
		cmd = exec.CommandContext(ctx, "pg_dump",
			"-h", cfg.DB.Host,
			"-p", fmt.Sprint(cfg.DB.Port),
			"-U", cfg.DB.User,
			"-d", cfg.DB.DB,
			"-f", filename,
			"--no-owner",
			"--no-acl",
			"--clean",
			"--if-exists",
		)
	}

	// Set password via environment variable
	cmd.Env = append(os.Environ(),
		fmt.Sprintf("PGPASSWORD=%s", cfg.DB.Password))

	// Run backup
	if err := cmd.Run(); err != nil {
		return "", errors.Wrap(err, "pg_dump failed")
	}

	// Get file size for logging
	info, err := os.Stat(filename)
	if err != nil {
		return filename, errors.Wrap(err, "stat backup file")
	}

	sizeMB := float64(info.Size()) / 1024 / 1024
	fmt.Printf("[INFO] Backup created: %s (%.2f MB)\n", filename, sizeMB)

	return filename, nil
}

// CleanOldBackups keeps only the N most recent backups
func CleanOldBackups(cfg *config.Config, keepCount int) error {
	// Get all backup files (both .sql and .sql.gz)
	sqlFiles, err := filepath.Glob(filepath.Join(cfg.DB.BackupDir, "*.sql"))
	if err != nil {
		return errors.Wrap(err, "failed to list .sql backups")
	}

	gzFiles, err := filepath.Glob(filepath.Join(cfg.DB.BackupDir, "*.sql.gz"))
	if err != nil {
		return errors.Wrap(err, "failed to list .sql.gz backups")
	}

	files := append(sqlFiles, gzFiles...)

	if len(files) <= keepCount {
		return nil // Nothing to clean
	}

	// Sort files by modification time (newest first)
	sort.Slice(files, func(i, j int) bool {
		iInfo, _ := os.Stat(files[i])
		jInfo, _ := os.Stat(files[j])
		return iInfo.ModTime().After(jInfo.ModTime())
	})

	// Delete old backups
	for i := keepCount; i < len(files); i++ {
		if err := os.Remove(files[i]); err != nil {
			fmt.Printf("[WARN] Failed to remove old backup %s: %v\n", files[i], err)
		} else {
			fmt.Printf("[INFO] Removed old backup: %s\n", filepath.Base(files[i]))
		}
	}

	return nil
}
