package core

import (
	"context"
	"fmt"
	"log/slog"
	"sort"
	"time"

	"github.com/pocketbase/pocketbase/tools/inflector"
)

const (
	StoreKeyActiveBackup = "@activeBackup"
)

// generateBackupName generates a new backup name based on the app name and current date.
func generateBackupName(app App, prefix string) string {
	appName := inflector.Snakecase(app.Settings().Meta.AppName)
	if len(appName) > 50 {
		appName = appName[:50]
	}

	return fmt.Sprintf(
		"%s%s_%s.zip",
		prefix,
		appName,
		time.Now().UTC().Format("20060102150405"),
	)
}

// registerAutobackupHooks registers the autobackup app serve hooks.
func (app *BaseApp) registerAutobackupHooks() {
	const jobId = "__pbAutoBackup__"

	loadJob := func() {
		rawSchedule := app.Settings().Backups.Cron
		if rawSchedule == "" {
			app.Cron().Remove(jobId)
			return
		}

		app.Cron().Add(jobId, rawSchedule, func() {
			const autoPrefix = "@auto_pb_backup_"

			name := generateBackupName(app, autoPrefix)

			if err := app.CreateBackup(context.Background(), name); err != nil {
				app.Logger().Error(
					"[Backup cron] Failed to create backup",
					slog.String("name", name),
					slog.String("error", err.Error()),
				)

				alertError := sendSystemAlertToAllSuperusers(
					app,
					"Autobackup failure",
					"Failed to create/upload automated backup. Raw error:\n"+err.Error(),
				)
				if alertError != nil {
					app.Logger().Warn(
						"[Backup cron] Failed to send backup error alerts",
						slog.String("name", name),
						slog.String("error", alertError.Error()),
					)
				}
			}

			maxKeep := app.Settings().Backups.CronMaxKeep

			if maxKeep == 0 {
				return // no explicit limit
			}

			fsys, err := app.NewBackupsFilesystem()
			if err != nil {
				app.Logger().Error(
					"[Backup cron] Failed to initialize the backup filesystem",
					slog.String("error", err.Error()),
				)
				return
			}
			defer fsys.Close()

			files, err := fsys.List(autoPrefix)
			if err != nil {
				app.Logger().Error(
					"[Backup cron] Failed to list autogenerated backups",
					slog.String("error", err.Error()),
				)
				return
			}

			if maxKeep >= len(files) {
				return // nothing to remove
			}

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

			// keep only the most recent n auto backup files
			toRemove := files[maxKeep:]

			for _, f := range toRemove {
				if err := fsys.Delete(f.Key); err != nil {
					app.Logger().Error(
						"[Backup cron] Failed to remove old autogenerated backup",
						slog.String("key", f.Key),
						slog.String("error", err.Error()),
					)
				}
			}
		})
	}

	app.OnBootstrap().BindFunc(func(e *BootstrapEvent) error {
		if err := e.Next(); err != nil {
			return err
		}

		loadJob()

		return nil
	})

	app.OnSettingsReload().BindFunc(func(e *SettingsReloadEvent) error {
		if err := e.Next(); err != nil {
			return err
		}

		loadJob()

		return nil
	})
}
