package services

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

	"go.uber.org/zap"
)

// BackupService gère les sauvegardes de base de données
// BE-DB-016: Implement automated database backups with retention policy
type BackupService struct {
	logger        *zap.Logger
	backupDir     string
	retentionDays int
	databaseURL   string
	databaseName  string
	databaseUser  string
	databaseHost  string
	databasePort  string
}

// BackupConfig contient la configuration pour les sauvegardes
type BackupConfig struct {
	BackupDir     string // Répertoire où stocker les backups
	RetentionDays int    // Nombre de jours de rétention
	DatabaseURL   string // URL de connexion à la base de données
	DatabaseName  string // Nom de la base de données
	DatabaseUser  string // Utilisateur de la base de données
	DatabaseHost  string // Hôte de la base de données
	DatabasePort  string // Port de la base de données
}

// BackupResult contient les informations sur une sauvegarde
type BackupResult struct {
	Success      bool
	BackupPath   string
	BackupSize   int64
	Duration     time.Duration
	ErrorMessage string
	CreatedAt    time.Time
}

// NewBackupService crée un nouveau service de backup
func NewBackupService(config BackupConfig, logger *zap.Logger) (*BackupService, error) {
	// Créer le répertoire de backup s'il n'existe pas
	if err := os.MkdirAll(config.BackupDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create backup directory: %w", err)
	}

	// Valeurs par défaut
	retentionDays := config.RetentionDays
	if retentionDays <= 0 {
		retentionDays = 30 // 30 jours par défaut
	}

	return &BackupService{
		logger:        logger,
		backupDir:     config.BackupDir,
		retentionDays: retentionDays,
		databaseURL:   config.DatabaseURL,
		databaseName:  config.DatabaseName,
		databaseUser:  config.DatabaseUser,
		databaseHost:  config.DatabaseHost,
		databasePort:  config.DatabasePort,
	}, nil
}

// CreateBackup crée une sauvegarde de la base de données
func (bs *BackupService) CreateBackup(ctx context.Context) (*BackupResult, error) {
	startTime := time.Now()

	// Générer le nom du fichier de backup avec timestamp
	timestamp := time.Now().Format("20060102_150405")
	backupFileName := fmt.Sprintf("%s_%s.sql", bs.databaseName, timestamp)
	backupPath := filepath.Join(bs.backupDir, backupFileName)

	bs.logger.Info("Creating database backup",
		zap.String("database", bs.databaseName),
		zap.String("backup_path", backupPath))

	// Construire la commande pg_dump
	// Utiliser pg_dump avec format custom pour compression
	var cmd *exec.Cmd
	if bs.databaseURL != "" {
		// Utiliser DATABASE_URL si disponible
		cmd = exec.CommandContext(ctx, "pg_dump", "-Fc", "-f", backupPath, bs.databaseURL)
	} else {
		// Utiliser les paramètres individuels
		args := []string{
			"-h", bs.databaseHost,
			"-p", bs.databasePort,
			"-U", bs.databaseUser,
			"-Fc", // Format custom (compressed)
			"-f", backupPath,
			bs.databaseName,
		}
		cmd = exec.CommandContext(ctx, "pg_dump", args...)
		// Définir PGPASSWORD si nécessaire
		if password := os.Getenv("PGPASSWORD"); password != "" {
			cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", password))
		}
	}

	// Exécuter la commande
	output, err := cmd.CombinedOutput()
	if err != nil {
		bs.logger.Error("Failed to create backup",
			zap.Error(err),
			zap.String("output", string(output)))
		return &BackupResult{
			Success:      false,
			ErrorMessage: fmt.Sprintf("pg_dump failed: %v: %s", err, string(output)),
			Duration:     time.Since(startTime),
			CreatedAt:    time.Now(),
		}, fmt.Errorf("backup failed: %w", err)
	}

	// Obtenir la taille du fichier
	fileInfo, err := os.Stat(backupPath)
	if err != nil {
		bs.logger.Warn("Failed to get backup file size", zap.Error(err))
	}

	var backupSize int64
	if fileInfo != nil {
		backupSize = fileInfo.Size()
	}

	duration := time.Since(startTime)

	bs.logger.Info("Backup created successfully",
		zap.String("backup_path", backupPath),
		zap.Int64("backup_size", backupSize),
		zap.Duration("duration", duration))

	return &BackupResult{
		Success:    true,
		BackupPath: backupPath,
		BackupSize: backupSize,
		Duration:   duration,
		CreatedAt:  time.Now(),
	}, nil
}

// CleanupOldBackups supprime les anciennes sauvegardes selon la politique de rétention
func (bs *BackupService) CleanupOldBackups(ctx context.Context) error {
	bs.logger.Info("Cleaning up old backups",
		zap.Int("retention_days", bs.retentionDays))

	cutoffTime := time.Now().AddDate(0, 0, -bs.retentionDays)

	// Lire le répertoire de backup
	entries, err := os.ReadDir(bs.backupDir)
	if err != nil {
		return fmt.Errorf("failed to read backup directory: %w", err)
	}

	deletedCount := 0
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		// Vérifier si le fichier correspond au pattern de backup
		ext := filepath.Ext(entry.Name())
		if ext != ".sql" && ext != ".dump" {
			continue
		}

		// Obtenir les informations du fichier
		filePath := filepath.Join(bs.backupDir, entry.Name())
		fileInfo, err := entry.Info()
		if err != nil {
			bs.logger.Warn("Failed to get file info", zap.String("file", entry.Name()), zap.Error(err))
			continue
		}

		// Vérifier si le fichier est plus ancien que la date de rétention
		if fileInfo.ModTime().Before(cutoffTime) {
			if err := os.Remove(filePath); err != nil {
				bs.logger.Error("Failed to delete old backup",
					zap.String("file", entry.Name()),
					zap.Error(err))
				continue
			}

			deletedCount++
			bs.logger.Info("Deleted old backup",
				zap.String("file", entry.Name()),
				zap.Time("modified", fileInfo.ModTime()))
		}
	}

	bs.logger.Info("Cleanup completed",
		zap.Int("deleted_count", deletedCount))

	return nil
}

// ListBackups liste toutes les sauvegardes disponibles
func (bs *BackupService) ListBackups(ctx context.Context) ([]BackupInfo, error) {
	entries, err := os.ReadDir(bs.backupDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read backup directory: %w", err)
	}

	var backups []BackupInfo
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		// Vérifier si le fichier correspond au pattern de backup
		ext := filepath.Ext(entry.Name())
		if ext != ".sql" && ext != ".dump" {
			continue
		}

		filePath := filepath.Join(bs.backupDir, entry.Name())
		fileInfo, err := entry.Info()
		if err != nil {
			continue
		}

		backups = append(backups, BackupInfo{
			FileName:  entry.Name(),
			FilePath:  filePath,
			Size:      fileInfo.Size(),
			CreatedAt: fileInfo.ModTime(),
		})
	}

	return backups, nil
}

// BackupInfo contient les informations sur une sauvegarde
type BackupInfo struct {
	FileName  string
	FilePath  string
	Size      int64
	CreatedAt time.Time
}
