package upgrade

import (
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/tj/go-update"
	"github.com/tj/go-update/stores/apex"
	"github.com/tj/go-update/stores/github"
	"github.com/tj/go/env"
	"github.com/tj/go/http/request"
	"github.com/tj/go/term"
	"github.com/tj/kingpin"

	"github.com/apex/up/internal/cli/root"
	"github.com/apex/up/internal/progressreader"
	"github.com/apex/up/internal/stats"
	"github.com/apex/up/internal/userconfig"
	"github.com/apex/up/internal/util"
)

var releasesAPI = env.GetDefault("APEX_RELEASES_API", "https://releases.apex.sh")

func init() {
	cmd := root.Command("upgrade", "Install the latest or specified version of Up.")
	cmd.Example(`up upgrade`, "Upgrade to the latest version available.")
	cmd.Example(`up upgrade -t 0.4.4`, "Upgrade to the specified version.")
	target := cmd.Flag("target", "Target version for upgrade.").Short('t').String()

	cmd.Action(func(_ *kingpin.ParseContext) error {
		version := root.Cmd.GetVersion()
		start := time.Now()

		term.HideCursor()
		defer term.ShowCursor()

		var config userconfig.Config
		if err := config.Load(); err != nil {
			return errors.Wrap(err, "loading user config")
		}

		// open-source edition
		p := &update.Manager{
			Command: "up",
			Store: &github.Store{
				Owner:   "apex",
				Repo:    "up",
				Version: version,
			},
		}

		// commercial edition
		if t := config.GetActiveTeam(); t != nil {
			// we pass 0.0.0 here beause the OSS
			// binary should always upgrade to Pro
			// regardless of versions matching.
			p.Store = &apex.Store{
				URL:       releasesAPI,
				Product:   "up",
				Version:   "0.0.0",
				Plan:      "pro",
				AccessKey: t.Token,
			}
		}

		// fetch latest or specified release
		r, err := getLatestOrSpecified(p, *target)
		if err != nil {
			return errors.Wrap(err, "fetching latest release")
		}

		// no updates
		if r == nil {
			util.LogPad("No updates available, you're good :)")
			return nil
		}

		// find the tarball for this system
		a := r.FindTarball(runtime.GOOS, runtime.GOARCH)
		if a == nil {
			return errors.Errorf("failed to find a binary for %s %s", runtime.GOOS, runtime.GOARCH)
		}

		// download tarball to a tmp dir
		var tarball string
		if util.IsCI() {
			tarball, err = a.Download()
			if err != nil {
				return errors.Wrap(err, "downloading tarball")
			}
		} else {
			tarball, err = a.DownloadProxy(progressreader.New)
			if err != nil {
				return errors.Wrap(err, "downloading tarball")
			}
		}

		// determine path
		path, err := exec.LookPath(os.Args[0])
		if err != nil {
			return errors.Wrap(err, "looking up executable path")
		}
		dst := filepath.Dir(path)

		// install it
		if err := p.InstallTo(tarball, dst); err != nil {
			return errors.Wrap(err, "installing")
		}

		term.ClearAll()

		if strings.Contains(a.URL, "up/pro") {
			util.LogPad("Updated %s to %s Pro", versionName(version), r.Version)
		} else {
			util.LogPad("Updated %s to %s OSS", versionName(version), r.Version)
		}

		stats.Track("Upgrade", map[string]interface{}{
			"from":     version,
			"to":       r.Version,
			"duration": time.Since(start).Round(time.Millisecond),
		})

		return nil
	})
}

// getLatestOrSpecified returns the latest or specified release.
func getLatestOrSpecified(s update.Store, version string) (*update.Release, error) {
	if version == "" {
		return getLatest(s)
	}

	return s.GetRelease(version)
}

// getLatest returns the latest release, error, or nil when there is none.
func getLatest(s update.Store) (*update.Release, error) {
	releases, err := s.LatestReleases()

	if request.IsClient(err) {
		return nil, errors.Wrap(err, "You're not subscribed to Up Pro")
	}

	if err != nil {
		return nil, errors.Wrap(err, "fetching releases")
	}

	if len(releases) == 0 {
		return nil, nil
	}

	return releases[0], nil
}

// versionName returns the humanized version name.
func versionName(s string) string {
	if strings.Contains(s, "-pro") {
		return strings.Replace(s, "-pro", "", 1) + " Pro"
	}

	return s + " OSS"
}
