package internal

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"coopcloud.tech/abra/pkg/client"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/dns"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/git"
	"coopcloud.tech/abra/pkg/lint"
	"coopcloud.tech/abra/pkg/recipe"
	"coopcloud.tech/abra/pkg/upstream/stack"
	"github.com/AlecAivazis/survey/v2"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// DeployAction is the main command-line action for this package
func DeployAction(c *cli.Context) error {
	app := ValidateApp(c)

	if !Chaos {
		if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
			logrus.Fatal(err)
		}
	}

	r, err := recipe.Get(app.Recipe)
	if err != nil {
		logrus.Fatal(err)
	}

	if err := lint.LintForErrors(r); err != nil {
		logrus.Fatal(err)
	}

	cl, err := client.New(app.Server)
	if err != nil {
		logrus.Fatal(err)
	}

	logrus.Debugf("checking whether %s is already deployed", app.StackName())

	isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName())
	if err != nil {
		logrus.Fatal(err)
	}

	if isDeployed {
		if Force || Chaos {
			logrus.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name)
		} else {
			logrus.Fatalf("%s is already deployed", app.Name)
		}
	}

	version := deployedVersion
	if version == "unknown" && !Chaos {
		catl, err := recipe.ReadRecipeCatalogue()
		if err != nil {
			logrus.Fatal(err)
		}
		versions, err := recipe.GetRecipeCatalogueVersions(app.Recipe, catl)
		if err != nil {
			logrus.Fatal(err)
		}
		if len(versions) > 0 {
			version = versions[len(versions)-1]
			logrus.Debugf("choosing %s as version to deploy", version)
			if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
				logrus.Fatal(err)
			}
		} else {
			head, err := git.GetRecipeHead(app.Recipe)
			if err != nil {
				logrus.Fatal(err)
			}
			version = formatter.SmallSHA(head.String())
			logrus.Warn("no versions detected, using latest commit")
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
				logrus.Fatal(err)
			}
		}
	}

	if version == "unknown" && !Chaos {
		logrus.Debugf("choosing %s as version to deploy", version)
		if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
			logrus.Fatal(err)
		}
	}

	if version != "unknown" && !Chaos {
		if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
			logrus.Fatal(err)
		}
	}

	if Chaos {
		logrus.Warnf("chaos mode engaged")
		var err error
		version, err = recipe.ChaosVersion(app.Recipe)
		if err != nil {
			logrus.Fatal(err)
		}
	}

	abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
	abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
	if err != nil {
		logrus.Fatal(err)
	}
	for k, v := range abraShEnv {
		app.Env[k] = v
	}

	composeFiles, err := config.GetAppComposeFiles(app.Recipe, app.Env)
	if err != nil {
		logrus.Fatal(err)
	}
	deployOpts := stack.Deploy{
		Composefiles: composeFiles,
		Namespace:    app.StackName(),
		Prune:        false,
		ResolveImage: stack.ResolveImageAlways,
	}
	compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
	if err != nil {
		logrus.Fatal(err)
	}

	if err := DeployOverview(app, version, "continue with deployment?"); err != nil {
		logrus.Fatal(err)
	}

	if !NoDomainChecks {
		domainName := app.Env["DOMAIN"]
		if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil {
			logrus.Fatal(err)
		}
	} else {
		logrus.Warn("skipping domain checks as requested")
	}

	if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, DontWaitConverge); err != nil {
		logrus.Fatal(err)
	}

	return nil
}

// DeployOverview shows a deployment overview
func DeployOverview(app config.App, version, message string) error {
	tableCol := []string{"server", "recipe", "config", "domain", "version"}
	table := formatter.CreateTable(tableCol)

	deployConfig := "compose.yml"
	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
		deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
	}

	server := app.Server
	if app.Server == "default" {
		server = "local"
	}

	table.Append([]string{server, app.Recipe, deployConfig, app.Domain, version})
	table.Render()

	if NoInput {
		return nil
	}

	response := false
	prompt := &survey.Confirm{
		Message: message,
	}

	if err := survey.AskOne(prompt, &response); err != nil {
		return err
	}

	if !response {
		logrus.Fatal("exiting as requested")
	}

	return nil
}

// NewVersionOverview shows an upgrade or downgrade overview
func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes string) error {
	tableCol := []string{"server", "recipe", "config", "domain", "current version", "to be deployed"}
	table := formatter.CreateTable(tableCol)

	deployConfig := "compose.yml"
	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
		deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
	}

	server := app.Server
	if app.Server == "default" {
		server = "local"
	}

	table.Append([]string{server, app.Recipe, deployConfig, app.Domain, currentVersion, newVersion})
	table.Render()

	if releaseNotes == "" {
		var err error
		releaseNotes, err = GetReleaseNotes(app.Recipe, newVersion)
		if err != nil {
			return err
		}
	}

	if releaseNotes != "" && newVersion != "" {
		fmt.Println()
		fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes))
	} else {
		logrus.Warnf("no release notes available for %s", newVersion)
	}

	if NoInput {
		return nil
	}

	response := false
	prompt := &survey.Confirm{
		Message: "continue with deployment?",
	}

	if err := survey.AskOne(prompt, &response); err != nil {
		return err
	}

	if !response {
		logrus.Fatal("exiting as requested")
	}

	return nil
}

// GetReleaseNotes prints release notes for a recipe version
func GetReleaseNotes(recipeName, version string) (string, error) {
	if version == "" {
		return "", nil
	}

	fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version)

	if _, err := os.Stat(fpath); !os.IsNotExist(err) {
		releaseNotes, err := ioutil.ReadFile(fpath)
		if err != nil {
			return "", err
		}
		return string(releaseNotes), nil
	}

	return "", nil
}