// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0

package cli

import (
	"fmt"
	"io"
	"io/ioutil"
	"os/exec"
	"strings"
	"syscall"

	"git.arvados.org/arvados.git/lib/cmd"
)

var (
	Create = rubyArvCmd{"create"}
	Edit   = rubyArvCmd{"edit"}

	Copy = externalCmd{"arv-copy"}
	Tag  = externalCmd{"arv-tag"}
	Ws   = externalCmd{"arv-ws"}

	Keep = cmd.Multi(map[string]cmd.Handler{
		"get":       externalCmd{"arv-get"},
		"put":       externalCmd{"arv-put"},
		"ls":        externalCmd{"arv-ls"},
		"normalize": externalCmd{"arv-normalize"},
		"docker":    externalCmd{"arv-keepdocker"},
	})
	// user, group, container, specimen, etc.
	APICall = apiCallCmd{}
)

// When using the ruby "arv" command, flags must come before the
// subcommand: "arv --format=yaml get foo" works, but "arv get
// --format=yaml foo" does not work.
func legacyFlagsToFront(subcommand string, argsin []string) (argsout []string) {
	flags, _ := LegacyFlagSet()
	flags.SetOutput(ioutil.Discard)
	flags.Parse(argsin)
	narg := flags.NArg()
	argsout = append(argsout, argsin[:len(argsin)-narg]...)
	argsout = append(argsout, subcommand)
	argsout = append(argsout, argsin[len(argsin)-narg:]...)
	return
}

type apiCallCmd struct{}

func (cmd apiCallCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
	split := strings.Split(prog, " ")
	if len(split) < 2 {
		fmt.Fprintf(stderr, "internal error: no api model in %q\n", prog)
		return 2
	}
	model := split[len(split)-1]
	return rubyArvCmd{model}.RunCommand(prog, args, stdin, stdout, stderr)
}

type rubyArvCmd struct {
	subcommand string
}

func (rc rubyArvCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
	wrapprog := "arv-ruby"
	if _, err := exec.LookPath(wrapprog); err != nil && !strings.Contains(prog, "arv ") {
		// arv-ruby isn't in PATH (i.e., installation method
		// wasn't a recent "arvados-server install", which
		// symlinks /usr/bin/arv-ruby ->
		// /var/lib/arvados/bin/arv), so fall back to looking
		// for the arvados-cli program as "arv". (But don't do
		// this if we are being run as "arv" -- that would
		// probably cause a recursive fork bomb.)
		wrapprog = "arv"
	}
	return externalCmd{wrapprog}.RunCommand(wrapprog, legacyFlagsToFront(rc.subcommand, args), stdin, stdout, stderr)
}

type externalCmd struct {
	prog string
}

func (ec externalCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
	cmd := exec.Command(ec.prog, args...)
	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	err := cmd.Run()
	switch err := err.(type) {
	case nil:
		return 0
	case *exec.ExitError:
		status := err.Sys().(syscall.WaitStatus)
		if status.Exited() {
			return status.ExitStatus()
		}
		fmt.Fprintf(stderr, "%s failed: %s\n", ec.prog, err)
		return 1
	case *exec.Error:
		fmt.Fprintln(stderr, err)
		if ec.prog == "arv" || ec.prog == "arv-ruby" {
			fmt.Fprint(stderr, rubyInstallHints)
		} else if strings.HasPrefix(ec.prog, "arv-") {
			fmt.Fprint(stderr, pythonInstallHints)
		}
		return 1
	default:
		fmt.Fprintf(stderr, "error running %s: %s\n", ec.prog, err)
		return 1
	}
}

var (
	rubyInstallHints = `
Note: This subcommand uses the arvados-cli Ruby gem. If that is not
installed, try "gem install arvados-cli", or see
https://doc.arvados.org/install for more details.

`
	pythonInstallHints = `
Note: This subcommand uses the "arvados" Python module. If that is
not installed, try:
* "pip install arvados" (either as root or in a virtualenv), or
* "sudo apt-get install python3-arvados-python-client", or
* see https://doc.arvados.org/install for more details.

`
)
