package main

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/alcionai/clues"
	"github.com/spf13/cobra"
	"golang.org/x/exp/maps"

	"github.com/alcionai/corso/src/cli/config"
	"github.com/alcionai/corso/src/cli/utils"
	"github.com/alcionai/corso/src/pkg/logger"
	"github.com/alcionai/corso/src/pkg/path"
	"github.com/alcionai/corso/src/pkg/repository"
	"github.com/alcionai/corso/src/pkg/store"
)

// deleteBackups connects to the repository and deletes all backups for
// service that are at least deletionDays old. Returns the IDs of all backups
// that were deleted.
func deleteBackups(
	ctx context.Context,
	service path.ServiceType,
	deletionDays int,
) ([]string, error) {
	ctx = clues.Add(ctx, "cutoff_days", deletionDays)

	r, _, _, _, err := utils.GetAccountAndConnect(ctx, service, nil)
	if err != nil {
		return nil, clues.Wrap(err, "connecting to account").WithClues(ctx)
	}

	defer r.Close(ctx)

	backups, err := r.BackupsByTag(ctx, store.Service(service))
	if err != nil {
		return nil, clues.Wrap(err, "listing backups").WithClues(ctx)
	}

	var (
		deleted []string
		cutoff  = time.Now().Add(-time.Hour * 24 * time.Duration(deletionDays))
	)

	for _, backup := range backups {
		if backup.StartAndEndTime.CompletedAt.Before(cutoff) {
			if err := r.DeleteBackup(ctx, backup.ID.String()); err != nil {
				return nil, clues.Wrap(
					err,
					"deleting backup").
					With("backup_id", backup.ID).
					WithClues(ctx)
			}

			deleted = append(deleted, backup.ID.String())
			logAndPrint(ctx, "Deleted backup %s", backup.ID.String())
		}
	}

	return deleted, nil
}

// pitrListBackups connects to the repository at the given point in time and
// lists the backups for service. It then checks the list of backups contains
// the backups in backupIDs.
//
//nolint:unused
//lint:ignore U1000 Waiting for full support.
func pitrListBackups(
	ctx context.Context,
	service path.ServiceType,
	pitr time.Time,
	backupIDs []string,
) error {
	if len(backupIDs) == 0 {
		return nil
	}

	ctx = clues.Add(ctx, "pitr_time", pitr, "search_backups", backupIDs)

	// TODO(ashmrtn): This may be moved into CLI layer at some point when we add
	// flags for opening a repo at a point in time.
	cfg, err := config.GetConfigRepoDetails(ctx, true, true, nil)
	if err != nil {
		return clues.Wrap(err, "getting config info")
	}

	opts := utils.ControlWithConfig(cfg)
	opts.Repo.ViewTimestamp = &pitr

	r, err := repository.Connect(ctx, cfg.Account, cfg.Storage, cfg.RepoID, opts)
	if err != nil {
		return clues.Wrap(err, "connecting to repo").WithClues(ctx)
	}

	defer r.Close(ctx)

	backups, err := r.BackupsByTag(ctx, store.Service(service))
	if err != nil {
		return clues.Wrap(err, "listing backups").WithClues(ctx)
	}

	bups := map[string]struct{}{}

	for _, backup := range backups {
		bups[backup.ID.String()] = struct{}{}
	}

	ctx = clues.Add(ctx, "found_backups", maps.Keys(bups))

	for _, backupID := range backupIDs {
		if _, ok := bups[backupID]; !ok {
			return clues.New("looking for backup").
				With("search_backup_id", backupID).
				WithClues(ctx)
		}
	}

	return nil
}

func main() {
	var (
		service path.ServiceType
		cc      = cobra.Command{}
	)

	cc.SetContext(context.Background())

	if err := config.InitFunc(&cc, []string{}); err != nil {
		return
	}

	switch serviceName := os.Getenv("SERVICE"); serviceName {
	case "exchange":
		service = path.ExchangeService
	case "onedrive":
		service = path.OneDriveService
	case "sharepoint":
		service = path.SharePointService
	default:
		fatal(cc.Context(), "unknown service", nil)
	}

	ctx := clues.Add(cc.Context(), "service", service)

	days, err := strconv.Atoi(os.Getenv("DELETION_DAYS"))
	if err != nil {
		fatal(ctx, "invalid number of days provided", nil)
	}

	_, err = deleteBackups(ctx, service, days)
	if err != nil {
		fatal(cc.Context(), "deleting backups", clues.Stack(err))
	}
}

func fatal(ctx context.Context, msg string, err error) {
	logger.CtxErr(ctx, err).Error("test failure: " + msg)
	fmt.Println(msg+": ", err)
	os.Exit(1)
}

func logAndPrint(ctx context.Context, tmpl string, vs ...any) {
	logger.Ctx(ctx).Infof(tmpl, vs...)
	fmt.Printf(tmpl+"\n", vs...)
}
