package main

import (
	"io"
	"os"
	"time"

	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/terminal"
	"github.com/restic/restic/internal/ui"
	"github.com/restic/restic/internal/ui/progress"
	"github.com/spf13/cobra"
	"github.com/spf13/cobra/doc"
	"github.com/spf13/pflag"
)

func newGenerateCommand() *cobra.Command {
	var opts generateOptions

	cmd := &cobra.Command{
		Use:   "generate [flags]",
		Short: "Generate manual pages and auto-completion files (bash, fish, zsh, powershell)",
		Long: `
The "generate" command writes automatically generated files (like the man pages
and the auto-completion files for bash, fish and zsh).

EXIT STATUS
===========

Exit status is 0 if the command was successful.
Exit status is 1 if there was any error.
`,
		DisableAutoGenTag: true,
		RunE: func(_ *cobra.Command, args []string) error {
			term, cancel := setupTermstatus()
			defer cancel()
			return runGenerate(opts, globalOptions, args, term)
		},
	}
	opts.AddFlags(cmd.Flags())
	return cmd
}

type generateOptions struct {
	ManDir                   string
	BashCompletionFile       string
	FishCompletionFile       string
	ZSHCompletionFile        string
	PowerShellCompletionFile string
}

func (opts *generateOptions) AddFlags(f *pflag.FlagSet) {
	f.StringVar(&opts.ManDir, "man", "", "write man pages to `directory`")
	f.StringVar(&opts.BashCompletionFile, "bash-completion", "", "write bash completion `file` (`-` for stdout)")
	f.StringVar(&opts.FishCompletionFile, "fish-completion", "", "write fish completion `file` (`-` for stdout)")
	f.StringVar(&opts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file` (`-` for stdout)")
	f.StringVar(&opts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)")
}

func writeManpages(root *cobra.Command, dir string, printer progress.Printer) error {
	// use a fixed date for the man pages so that generating them is deterministic
	date, err := time.Parse("Jan 2006", "Jan 2017")
	if err != nil {
		return err
	}

	header := &doc.GenManHeader{
		Title:   "restic backup",
		Section: "1",
		Source:  "generated by `restic generate`",
		Date:    &date,
	}

	printer.P("writing man pages to directory %v", dir)
	return doc.GenManTree(root, header, dir)
}

func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer) (err error) {
	if terminal.StdoutIsTerminal() {
		printer.P("writing %s completion file to %v", shell, filename)
	}
	var outWriter io.Writer
	if filename != "-" {
		var outFile *os.File
		outFile, err = os.Create(filename)
		if err != nil {
			return
		}
		defer func() { err = outFile.Close() }()
		outWriter = outFile
	} else {
		outWriter = globalOptions.stdout
	}

	err = generate(outWriter)
	return
}

func checkStdoutForSingleShell(opts generateOptions) error {
	completionFileOpts := []string{
		opts.BashCompletionFile,
		opts.FishCompletionFile,
		opts.ZSHCompletionFile,
		opts.PowerShellCompletionFile,
	}
	seenIsStdout := false
	for _, completionFileOpt := range completionFileOpts {
		if completionFileOpt == "-" {
			if seenIsStdout {
				return errors.Fatal("the generate command can generate shell completions to stdout for single shell only")
			}
			seenIsStdout = true
		}
	}
	return nil
}

func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
	if len(args) > 0 {
		return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
	}

	printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
	cmdRoot := newRootCommand()

	if opts.ManDir != "" {
		err := writeManpages(cmdRoot, opts.ManDir, printer)
		if err != nil {
			return err
		}
	}

	err := checkStdoutForSingleShell(opts)
	if err != nil {
		return err
	}

	if opts.BashCompletionFile != "" {
		err := writeCompletion(opts.BashCompletionFile, "bash", cmdRoot.GenBashCompletion, printer)
		if err != nil {
			return err
		}
	}

	if opts.FishCompletionFile != "" {
		err := writeCompletion(opts.FishCompletionFile, "fish", func(w io.Writer) error { return cmdRoot.GenFishCompletion(w, true) }, printer)
		if err != nil {
			return err
		}
	}

	if opts.ZSHCompletionFile != "" {
		err := writeCompletion(opts.ZSHCompletionFile, "zsh", cmdRoot.GenZshCompletion, printer)
		if err != nil {
			return err
		}
	}

	if opts.PowerShellCompletionFile != "" {
		err := writeCompletion(opts.PowerShellCompletionFile, "powershell", cmdRoot.GenPowerShellCompletion, printer)
		if err != nil {
			return err
		}
	}

	var empty generateOptions
	if opts == empty {
		return errors.Fatal("nothing to do, please specify at least one output file/dir")
	}

	return nil
}
