package retention

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

	"dbbackup/internal/metadata"
)

// Policy defines the retention rules
type Policy struct {
	RetentionDays int
	MinBackups    int
	DryRun        bool
}

// CleanupResult contains information about cleanup operations
type CleanupResult struct {
	TotalBackups        int
	EligibleForDeletion int
	Deleted             []string
	Kept                []string
	SpaceFreed          int64
	Errors              []error
}

// ApplyPolicy enforces the retention policy on backups in a directory
func ApplyPolicy(backupDir string, policy Policy) (*CleanupResult, error) {
	result := &CleanupResult{
		Deleted: make([]string, 0),
		Kept:    make([]string, 0),
		Errors:  make([]error, 0),
	}

	// List all backups in directory
	backups, err := metadata.ListBackups(backupDir)
	if err != nil {
		return nil, fmt.Errorf("failed to list backups: %w", err)
	}

	result.TotalBackups = len(backups)

	// Sort backups by timestamp (oldest first)
	sort.Slice(backups, func(i, j int) bool {
		return backups[i].Timestamp.Before(backups[j].Timestamp)
	})

	// Calculate cutoff date
	cutoffDate := time.Now().AddDate(0, 0, -policy.RetentionDays)

	// Determine which backups to delete
	for i, backup := range backups {
		// Always keep minimum number of backups (most recent ones)
		backupsRemaining := len(backups) - i
		if backupsRemaining <= policy.MinBackups {
			result.Kept = append(result.Kept, backup.BackupFile)
			continue
		}

		// Check if backup is older than retention period
		if backup.Timestamp.Before(cutoffDate) {
			result.EligibleForDeletion++

			if policy.DryRun {
				result.Deleted = append(result.Deleted, backup.BackupFile)
			} else {
				// Delete backup file and associated metadata
				if err := deleteBackup(backup.BackupFile); err != nil {
					result.Errors = append(result.Errors,
						fmt.Errorf("failed to delete %s: %w", backup.BackupFile, err))
				} else {
					result.Deleted = append(result.Deleted, backup.BackupFile)
					result.SpaceFreed += backup.SizeBytes
				}
			}
		} else {
			result.Kept = append(result.Kept, backup.BackupFile)
		}
	}

	return result, nil
}

// deleteBackup removes a backup file and all associated files
func deleteBackup(backupFile string) error {
	// Delete main backup file
	if err := os.Remove(backupFile); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to delete backup file: %w", err)
	}

	// Delete metadata file
	metaFile := backupFile + ".meta.json"
	if err := os.Remove(metaFile); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to delete metadata file: %w", err)
	}

	// Delete legacy .sha256 file if exists
	sha256File := backupFile + ".sha256"
	if err := os.Remove(sha256File); err != nil && !os.IsNotExist(err) {
		// Don't fail if .sha256 doesn't exist (new format)
	}

	// Delete legacy .info file if exists
	infoFile := backupFile + ".info"
	if err := os.Remove(infoFile); err != nil && !os.IsNotExist(err) {
		// Don't fail if .info doesn't exist (new format)
	}

	return nil
}

// GetOldestBackups returns the N oldest backups in a directory
func GetOldestBackups(backupDir string, count int) ([]*metadata.BackupMetadata, error) {
	backups, err := metadata.ListBackups(backupDir)
	if err != nil {
		return nil, err
	}

	// Sort by timestamp (oldest first)
	sort.Slice(backups, func(i, j int) bool {
		return backups[i].Timestamp.Before(backups[j].Timestamp)
	})

	if count > len(backups) {
		count = len(backups)
	}

	return backups[:count], nil
}

// GetNewestBackups returns the N newest backups in a directory
func GetNewestBackups(backupDir string, count int) ([]*metadata.BackupMetadata, error) {
	backups, err := metadata.ListBackups(backupDir)
	if err != nil {
		return nil, err
	}

	// Sort by timestamp (newest first)
	sort.Slice(backups, func(i, j int) bool {
		return backups[i].Timestamp.After(backups[j].Timestamp)
	})

	if count > len(backups) {
		count = len(backups)
	}

	return backups[:count], nil
}

// CleanupByPattern removes backups matching a specific pattern
func CleanupByPattern(backupDir, pattern string, policy Policy) (*CleanupResult, error) {
	result := &CleanupResult{
		Deleted: make([]string, 0),
		Kept:    make([]string, 0),
		Errors:  make([]error, 0),
	}

	// Find matching backup files
	searchPattern := filepath.Join(backupDir, pattern)
	matches, err := filepath.Glob(searchPattern)
	if err != nil {
		return nil, fmt.Errorf("failed to match pattern: %w", err)
	}

	// Filter to only .dump or .sql files
	var backupFiles []string
	for _, match := range matches {
		ext := filepath.Ext(match)
		if ext == ".dump" || ext == ".sql" {
			backupFiles = append(backupFiles, match)
		}
	}

	// Load metadata for matched backups
	var backups []*metadata.BackupMetadata
	for _, file := range backupFiles {
		meta, err := metadata.Load(file)
		if err != nil {
			// Skip files without metadata
			continue
		}
		backups = append(backups, meta)
	}

	result.TotalBackups = len(backups)

	// Sort by timestamp
	sort.Slice(backups, func(i, j int) bool {
		return backups[i].Timestamp.Before(backups[j].Timestamp)
	})

	cutoffDate := time.Now().AddDate(0, 0, -policy.RetentionDays)

	// Apply policy
	for i, backup := range backups {
		backupsRemaining := len(backups) - i
		if backupsRemaining <= policy.MinBackups {
			result.Kept = append(result.Kept, backup.BackupFile)
			continue
		}

		if backup.Timestamp.Before(cutoffDate) {
			result.EligibleForDeletion++

			if policy.DryRun {
				result.Deleted = append(result.Deleted, backup.BackupFile)
			} else {
				if err := deleteBackup(backup.BackupFile); err != nil {
					result.Errors = append(result.Errors, err)
				} else {
					result.Deleted = append(result.Deleted, backup.BackupFile)
					result.SpaceFreed += backup.SizeBytes
				}
			}
		} else {
			result.Kept = append(result.Kept, backup.BackupFile)
		}
	}

	return result, nil
}
