// Copyright 2025.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ssh_config_file

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

// createBackup creates a timestamped backup of the current config file
func (r *Repository) createBackup() error {
	if _, err := r.fileSystem.Stat(r.configPath); os.IsNotExist(err) {
		return nil
	} else if err != nil {
		return fmt.Errorf("failed to check if config file exists: %w", err)
	}

	timestamp := time.Now().UnixMilli()
	backupPath := fmt.Sprintf("%s-%d-%s", r.configPath, timestamp, BackupSuffix)

	if err := r.copyFile(r.configPath, backupPath); err != nil {
		return fmt.Errorf("failed to copy config to backup: %w", err)
	}

	r.logger.Infof("Created backup: %s", backupPath)

	configDir := filepath.Dir(r.configPath)

	backupFiles, err := r.findBackupFiles(configDir)
	if err != nil {
		return err
	}

	if len(backupFiles) <= MaxBackups {
		return nil
	}

	sort.Slice(backupFiles, func(i, j int) bool {
		return backupFiles[i].ModTime().After(backupFiles[j].ModTime())
	})

	for i := MaxBackups; i < len(backupFiles); i++ {
		backupPath := filepath.Join(configDir, backupFiles[i].Name())
		if err := r.fileSystem.Remove(backupPath); err != nil {
			r.logger.Warnf("failed to remove old backup %s: %v", backupPath, err)
			continue
		}
		r.logger.Infof("Removed old backup: %s", backupPath)
	}
	return nil
}

// copyFile copies a file from src to dst
func (r *Repository) copyFile(src, dst string) error {
	srcFile, err := r.fileSystem.Open(src)
	if err != nil {
		return err
	}
	defer func() {
		if cerr := srcFile.Close(); cerr != nil {
			r.logger.Warnf("failed to close source file %s: %v", src, cerr)
		}
	}()

	srcInfo, err := r.fileSystem.Stat(src)
	if err != nil {
		return err
	}

	destFile, err := r.fileSystem.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode())
	if err != nil {
		return err
	}
	defer func() {
		if cerr := destFile.Close(); cerr != nil {
			r.logger.Warnf("failed to close destination file %s: %v", dst, cerr)
		}
	}()

	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return err
	}

	return destFile.Sync()
}

// findBackupFiles finds all backup files for the given config file
func (r *Repository) findBackupFiles(dir string) ([]os.FileInfo, error) {
	entries, err := r.fileSystem.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	var backupFiles []os.FileInfo

	for _, entry := range entries {
		name := entry.Name()
		if strings.HasSuffix(name, BackupSuffix) {
			info, err := entry.Info()
			if err != nil {
				r.logger.Warnf("failed to get info for backup file %s: %v", name, err)
				continue
			}
			backupFiles = append(backupFiles, info)
		}
	}

	return backupFiles, nil
}

// createOriginalBackupIfNeeded creates a one-time original backup of the current SSH config.
func (r *Repository) createOriginalBackupIfNeeded() error {
	// If no SSH config file, nothing to do.
	if _, err := r.fileSystem.Stat(r.configPath); os.IsNotExist(err) {
		return nil
	} else if err != nil {
		return fmt.Errorf("failed to check if config file exists: %w", err)
	}

	configDir := filepath.Dir(r.configPath)
	originalBackupPath := filepath.Join(configDir, OriginalBackupName)

	if _, err := r.fileSystem.Stat(originalBackupPath); err == nil {
		return nil
	} else if !r.fileSystem.IsNotExist(err) {
		return fmt.Errorf("failed to check if original backup exists: %w", err)
	}

	if err := r.copyFile(r.configPath, originalBackupPath); err != nil {
		return fmt.Errorf("failed to create original backup: %w", err)
	}

	r.logger.Infof("Created original backup: %s", originalBackupPath)
	return nil
}
