package catalogue

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path"

	"coopcloud.tech/abra/cli/internal"
	"coopcloud.tech/abra/pkg/autocomplete"
	"coopcloud.tech/abra/pkg/catalogue"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/formatter"
	gitPkg "coopcloud.tech/abra/pkg/git"
	"coopcloud.tech/abra/pkg/log"
	"coopcloud.tech/abra/pkg/recipe"
	"github.com/go-git/go-git/v5"
	"github.com/urfave/cli"
)

var catalogueGenerateCommand = cli.Command{
	Name:    "generate",
	Aliases: []string{"g"},
	Usage:   "Generate the recipe catalogue",
	Flags: []cli.Flag{
		internal.DebugFlag,
		internal.NoInputFlag,
		internal.PublishFlag,
		internal.DryFlag,
		internal.SkipUpdatesFlag,
		internal.ChaosFlag,
		internal.OfflineFlag,
	},
	Before: internal.SubCommandBefore,
	Description: `
Generate a new copy of the recipe catalogue.

It is possible to generate new metadata for a single recipe by passing
<recipe>. The existing local catalogue will be updated, not overwritten.

It is quite easy to get rate limited by Docker Hub when running this command.
If you have a Hub account you can have Abra log you in to avoid this. Pass
"--user" and "--pass".

Push your new release to git.coopcloud.tech with "-p/--publish". This requires
that you have permission to git push to these repositories and have your SSH
keys configured on your account.`,
	ArgsUsage:    "[<recipe>]",
	BashComplete: autocomplete.RecipeNameComplete,
	Action: func(c *cli.Context) error {
		recipeName := c.Args().First()
		r := recipe.Get(recipeName)

		if recipeName != "" {
			internal.ValidateRecipe(c)
		}

		if !internal.Chaos {
			if err := catalogue.EnsureIsClean(); err != nil {
				log.Fatal(err)
			}
		}

		repos, err := recipe.ReadReposMetadata()
		if err != nil {
			log.Fatal(err)
		}

		var barLength int
		var logMsg string
		if recipeName != "" {
			barLength = 1
			logMsg = fmt.Sprintf("ensuring %v recipe is cloned & up-to-date", barLength)
		} else {
			barLength = len(repos)
			logMsg = fmt.Sprintf("ensuring %v recipes are cloned & up-to-date, this could take some time...", barLength)
		}

		if !internal.SkipUpdates {
			log.Warn(logMsg)
			if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
				log.Fatal(err)
			}
		}

		catl := make(recipe.RecipeCatalogue)
		catlBar := formatter.CreateProgressbar(barLength, "generating catalogue metadata...")
		for _, recipeMeta := range repos {
			if recipeName != "" && recipeName != recipeMeta.Name {
				catlBar.Add(1)
				continue
			}

			versions, err := r.GetRecipeVersions()
			if err != nil {
				log.Warn(err)
			}

			features, category, err := recipe.GetRecipeFeaturesAndCategory(r)
			if err != nil {
				log.Warn(err)
			}

			catl[recipeMeta.Name] = recipe.RecipeMeta{
				Name:          recipeMeta.Name,
				Repository:    recipeMeta.CloneURL,
				SSHURL:        recipeMeta.SSHURL,
				Icon:          recipeMeta.AvatarURL,
				DefaultBranch: recipeMeta.DefaultBranch,
				Description:   recipeMeta.Description,
				Website:       recipeMeta.Website,
				Versions:      versions,
				Category:      category,
				Features:      features,
			}

			catlBar.Add(1)
		}

		recipesJSON, err := json.MarshalIndent(catl, "", "    ")
		if err != nil {
			log.Fatal(err)
		}

		if recipeName == "" {
			if err := ioutil.WriteFile(config.RECIPES_JSON, recipesJSON, 0764); err != nil {
				log.Fatal(err)
			}
		} else {
			catlFS, err := recipe.ReadRecipeCatalogue(internal.Offline)
			if err != nil {
				log.Fatal(err)
			}

			catlFS[recipeName] = catl[recipeName]

			updatedRecipesJSON, err := json.MarshalIndent(catlFS, "", "    ")
			if err != nil {
				log.Fatal(err)
			}

			if err := ioutil.WriteFile(config.RECIPES_JSON, updatedRecipesJSON, 0764); err != nil {
				log.Fatal(err)
			}
		}

		log.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON)

		cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
		if internal.Publish {

			isClean, err := gitPkg.IsClean(cataloguePath)
			if err != nil {
				log.Fatal(err)
			}

			if isClean {
				if !internal.Dry {
					log.Fatalf("no changes discovered in %s, nothing to publish?", cataloguePath)
				}
			}

			msg := "chore: publish new catalogue release changes"
			if err := gitPkg.Commit(cataloguePath, msg, internal.Dry); err != nil {
				log.Fatal(err)
			}

			repo, err := git.PlainOpen(cataloguePath)
			if err != nil {
				log.Fatal(err)
			}

			sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, config.CATALOGUE_JSON_REPO_NAME)
			if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil {
				log.Fatal(err)
			}

			if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil {
				log.Fatal(err)
			}
		}

		repo, err := git.PlainOpen(cataloguePath)
		if err != nil {
			log.Fatal(err)
		}

		head, err := repo.Head()
		if err != nil {
			log.Fatal(err)
		}

		if !internal.Dry && internal.Publish {
			url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
			log.Infof("new changes published: %s", url)
		}

		if internal.Dry {
			log.Info("dry run: no changes published")
		}

		return nil
	},
}

// CatalogueCommand defines the `abra catalogue` command and sub-commands.
var CatalogueCommand = cli.Command{
	Name:      "catalogue",
	Usage:     "Manage the recipe catalogue",
	Aliases:   []string{"c"},
	ArgsUsage: "<recipe>",
	Subcommands: []cli.Command{
		catalogueGenerateCommand,
	},
}
