// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package cmd

import (
	"context"
	"fmt"
	"runtime"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/os/gproc"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gtag"

	"hotgo/internal/library/hggen/internal/utility/mlog"
)

var (
	Docker = cDocker{}
)

type cDocker struct {
	g.Meta `name:"docker" usage:"{cDockerUsage}" brief:"{cDockerBrief}" eg:"{cDockerEg}" dc:"{cDockerDc}"`
}

const (
	cDockerUsage = `gf docker [MAIN] [OPTION]`
	cDockerBrief = `build docker image for current GoFrame project`
	cDockerEg    = `
gf docker 
gf docker -t hub.docker.com/john/image:tag
gf docker -p -t hub.docker.com/john/image:tag
gf docker main.go
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -p -t hub.docker.com/john/image:tag
gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
`
	cDockerDc = `
The "docker" command builds the GF project to a docker images.
It runs "gf build" firstly to compile the project to binary file.
It then runs "docker build" command automatically to generate the docker image.
You should have docker installed, and there must be a Dockerfile in the root of the project.
`
	cDockerMainBrief        = `main file path for "gf build", it's "main.go" in default. empty string for no binary build`
	cDockerBuildBrief       = `binary build options before docker image build, it's "-a amd64 -s linux" in default`
	cDockerFileBrief        = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
	cDockerShellBrief       = `path of the shell file which is executed before docker build`
	cDockerPushBrief        = `auto push the docker image to docker registry if "-t" option passed`
	cDockerTagBrief         = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
	cDockerTagNameBrief     = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
	cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
	cDockerExtraBrief       = `extra build options passed to "docker image"`
)

func init() {
	gtag.Sets(g.MapStrStr{
		`cDockerUsage`:            cDockerUsage,
		`cDockerBrief`:            cDockerBrief,
		`cDockerEg`:               cDockerEg,
		`cDockerDc`:               cDockerDc,
		`cDockerMainBrief`:        cDockerMainBrief,
		`cDockerFileBrief`:        cDockerFileBrief,
		`cDockerShellBrief`:       cDockerShellBrief,
		`cDockerBuildBrief`:       cDockerBuildBrief,
		`cDockerPushBrief`:        cDockerPushBrief,
		`cDockerTagBrief`:         cDockerTagBrief,
		`cDockerTagNameBrief`:     cDockerTagNameBrief,
		`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
		`cDockerExtraBrief`:       cDockerExtraBrief,
	})
}

type cDockerInput struct {
	g.Meta      `name:"docker" config:"gfcli.docker"`
	Main        string   `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
	File        string   `name:"file"        short:"f"  brief:"{cDockerFileBrief}"  d:"manifest/docker/Dockerfile"`
	Shell       string   `name:"shell"       short:"s"  brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
	Build       string   `name:"build"       short:"b"  brief:"{cDockerBuildBrief}"`
	Tag         string   `name:"tag"         short:"t"  brief:"{cDockerTagBrief}"`
	TagName     string   `name:"tagName"     short:"tn" brief:"{cDockerTagNameBrief}"     v:"required-with:TagPrefixes"`
	TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
	Push        bool     `name:"push"        short:"p"  brief:"{cDockerPushBrief}" orphan:"true"`
	Extra       string   `name:"extra"       short:"e"  brief:"{cDockerExtraBrief}"`
}

type cDockerOutput struct{}

func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput, err error) {
	// Necessary check.
	if gproc.SearchBinary("docker") == "" {
		mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`)
	}

	mlog.Debugf(`docker command input: %+v`, in)

	// Binary build.
	if in.Main != "" && in.Build != "" {
		in.Build += " --exitWhenError"
		if in.Main != "" {
			if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
				mlog.Debugf(`build binary failed with error: %+v`, err)
				return
			}
		}
	}

	// Shell executing.
	if in.Shell != "" && gfile.Exists(in.Shell) {
		if err = c.exeDockerShell(ctx, in.Shell); err != nil {
			mlog.Debugf(`build docker failed with error: %+v`, err)
			return
		}
	}
	// Docker build.
	var (
		dockerBuildOptions string
		dockerTags         []string
		dockerTagBase      string
	)
	if len(in.TagPrefixes) > 0 {
		for _, tagPrefix := range in.TagPrefixes {
			tagPrefix = gstr.TrimRight(tagPrefix, "/")
			dockerTags = append(dockerTags, fmt.Sprintf(`%s/%s`, tagPrefix, in.TagName))
		}
	}
	if len(dockerTags) == 0 {
		dockerTags = []string{in.Tag}
	}
	for i, dockerTag := range dockerTags {
		if i > 0 {
			err = gproc.ShellRun(ctx, fmt.Sprintf(`docker tag %s %s`, dockerTagBase, dockerTag))
			if err != nil {
				return
			}
			continue
		}
		dockerTagBase = dockerTag
		dockerBuildOptions = ""
		if dockerTag != "" {
			dockerBuildOptions = fmt.Sprintf(`-t %s`, dockerTag)
		}
		if in.Extra != "" {
			dockerBuildOptions = fmt.Sprintf(`%s %s`, dockerBuildOptions, in.Extra)
		}
		err = gproc.ShellRun(ctx, fmt.Sprintf(`docker build -f %s . %s`, in.File, dockerBuildOptions))
		if err != nil {
			return
		}
	}

	// Docker push.
	if !in.Push {
		return
	}
	for _, dockerTag := range dockerTags {
		if dockerTag == "" {
			continue
		}
		err = gproc.ShellRun(ctx, fmt.Sprintf(`docker push %s`, dockerTag))
		if err != nil {
			return
		}
	}
	return
}

func (c cDocker) exeDockerShell(ctx context.Context, shellFilePath string) error {
	if gfile.ExtName(shellFilePath) == "sh" && runtime.GOOS == "windows" {
		mlog.Debugf(`ignore shell file "%s", as it cannot be run on windows system`, shellFilePath)
		return nil
	}
	return gproc.ShellRun(ctx, gfile.GetContents(shellFilePath))
}
