package main

import (
	"fmt"
	"io"
	"restic"
	"strings"
)

// CmdForget implements the 'forget' command.
type CmdForget struct {
	Last    int `short:"l" long:"keep-last" description:"keep the last n snapshots"`
	Hourly  int `short:"H" long:"keep-hourly" description:"keep the last n hourly snapshots"`
	Daily   int `short:"d" long:"keep-daily" description:"keep the last n daily snapshots"`
	Weekly  int `short:"w" long:"keep-weekly" description:"keep the last n weekly snapshots"`
	Monthly int `short:"m" long:"keep-monthly" description:"keep the last n monthly snapshots"`
	Yearly  int `short:"y" long:"keep-yearly" description:"keep the last n yearly snapshots"`

	Hostname string `long:"hostname" description:"only forget snapshots for the given hostname"`

	DryRun bool `short:"n" long:"dry-run" description:"do not delete anything, just print what would be done"`

	global *GlobalOptions
}

func init() {
	_, err := parser.AddCommand("forget",
		"removes snapshots from a repository",
		`
The forget command removes snapshots according to a policy. Please note
that this command really only deletes the snapshot object in the repo, which
is a reference to data stored there. In order to remove this (now
unreferenced) data after 'forget' was run successfully, see the 'prune'
command.
`,
		&CmdForget{global: &globalOpts})
	if err != nil {
		panic(err)
	}
}

// Usage returns usage information for 'forget'.
func (cmd CmdForget) Usage() string {
	return "[snapshot ID] ..."
}

func printSnapshots(w io.Writer, snapshots restic.Snapshots) {
	tab := NewTable()
	tab.Header = fmt.Sprintf("%-8s  %-19s  %-10s  %s", "ID", "Date", "Host", "Directory")
	tab.RowFormat = "%-8s  %-19s  %-10s  %s"

	for _, sn := range snapshots {
		if len(sn.Paths) == 0 {
			continue
		}
		id := sn.ID()
		tab.Rows = append(tab.Rows, []interface{}{id.Str(), sn.Time.Format(TimeFormat), sn.Hostname, sn.Paths[0]})

		if len(sn.Paths) > 1 {
			for _, path := range sn.Paths[1:] {
				tab.Rows = append(tab.Rows, []interface{}{"", "", "", path})
			}
		}
	}

	tab.Write(w)
}

// Execute runs the 'forget' command.
func (cmd CmdForget) Execute(args []string) error {
	repo, err := cmd.global.OpenRepository()
	if err != nil {
		return err
	}

	lock, err := lockRepoExclusive(repo)
	defer unlockRepo(lock)
	if err != nil {
		return err
	}

	err = repo.LoadIndex()
	if err != nil {
		return err
	}

	// first, process all snapshot IDs given as arguments
	for _, s := range args {
		id, err := restic.FindSnapshot(repo, s)
		if err != nil {
			return err
		}

		if !cmd.DryRun {
			err = repo.Backend().Remove(restic.SnapshotFile, id.String())
			if err != nil {
				return err
			}

			cmd.global.Verbosef("removed snapshot %v\n", id.Str())
		} else {
			cmd.global.Verbosef("would removed snapshot %v\n", id.Str())
		}
	}

	policy := restic.ExpirePolicy{
		Last:    cmd.Last,
		Hourly:  cmd.Hourly,
		Daily:   cmd.Daily,
		Weekly:  cmd.Weekly,
		Monthly: cmd.Monthly,
		Yearly:  cmd.Yearly,
	}

	if policy.Empty() {
		return nil
	}

	// then, load all remaining snapshots
	snapshots, err := restic.LoadAllSnapshots(repo)
	if err != nil {
		return err
	}

	// group by hostname and dirs
	type key struct {
		Hostname string
		Dirs     string
	}

	snapshotGroups := make(map[key]restic.Snapshots)

	for _, sn := range snapshots {
		if cmd.Hostname != "" && sn.Hostname != cmd.Hostname {
			continue
		}

		k := key{Hostname: sn.Hostname, Dirs: strings.Join(sn.Paths, ":")}
		list := snapshotGroups[k]
		list = append(list, sn)
		snapshotGroups[k] = list
	}

	for key, snapshotGroup := range snapshotGroups {
		cmd.global.Printf("snapshots for host %v, directories %v:\n\n", key.Hostname, key.Dirs)
		keep, remove := restic.ApplyPolicy(snapshotGroup, policy)

		cmd.global.Printf("keep %d snapshots:\n", len(keep))
		printSnapshots(cmd.global.stdout, keep)
		cmd.global.Printf("\n")

		cmd.global.Printf("remove %d snapshots:\n", len(remove))
		printSnapshots(cmd.global.stdout, remove)
		cmd.global.Printf("\n")

		if !cmd.DryRun {
			for _, sn := range remove {
				err = repo.Backend().Remove(restic.SnapshotFile, sn.ID().String())
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}
