package sdk

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"os"
	"path"
	"strings"

	"dagger.io/dagger"
	"github.com/dagger/dagger/internal/mage/util"
	"github.com/magefile/mage/mg"
)

const (
	goGeneratedAPIPath = "sdk/go/api.gen.go"
)

var _ SDK = Go{}

type Go mg.Namespace

// Lint lints the Go SDK
func (t Go) Lint(ctx context.Context) error {
	c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
	if err != nil {
		return err
	}
	defer c.Close()

	_, err = c.Container().
		From("golangci/golangci-lint:v1.48").
		WithMountedDirectory("/app", util.RepositoryGoCodeOnly(c)).
		WithWorkdir("/app/sdk/go").
		Exec(dagger.ContainerExecOpts{
			Args: []string{"golangci-lint", "run", "-v", "--timeout", "5m"},
		}).ExitCode(ctx)
	if err != nil {
		return err
	}

	return lintGeneratedCode(func() error {
		return t.Generate(ctx)
	}, goGeneratedAPIPath)
}

// Test tests the Go SDK
func (t Go) Test(ctx context.Context) error {
	c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
	if err != nil {
		return err
	}
	defer c.Close()

	return util.WithDevEngine(ctx, c, func(ctx context.Context, c *dagger.Client) error {
		_, err = util.GoBase(c).
			WithWorkdir("sdk/go").
			Exec(dagger.ContainerExecOpts{
				Args:                          []string{"go", "test", "-p", "16", "-v", "./..."},
				ExperimentalPrivilegedNesting: true,
			}).
			ExitCode(ctx)
		return err
	})
}

// Generate re-generates the SDK API
func (t Go) Generate(ctx context.Context) error {
	c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
	if err != nil {
		return err
	}
	defer c.Close()

	return util.WithDevEngine(ctx, c, func(ctx context.Context, c *dagger.Client) error {
		generated, err := util.GoBase(c).
			WithMountedFile("/usr/local/bin/cloak", util.DaggerBinary(c)).
			WithWorkdir("sdk/go").
			Exec(dagger.ContainerExecOpts{
				Args:                          []string{"go", "generate", "-v", "./..."},
				ExperimentalPrivilegedNesting: true,
			}).
			File(path.Base(goGeneratedAPIPath)).
			Contents(ctx)
		if err != nil {
			return err
		}
		return os.WriteFile(goGeneratedAPIPath, []byte(generated), 0600)
	})
}

// Publish publishes the Go SDK
func (t Go) Publish(ctx context.Context, tag string) error {
	c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
	if err != nil {
		return err
	}
	defer c.Close()

	var targetTag = strings.TrimPrefix(tag, "sdk/go/")

	var targetRepo = os.Getenv("TARGET_REPO")
	if targetRepo == "" {
		targetRepo = "https://github.com/dagger/dagger-go-sdk.git"
	}

	var pat = os.Getenv("GITHUB_PAT")
	if pat == "" {
		return errors.New("GITHUB_PAT environment variable must be set")
	}
	encodedPAT := base64.URLEncoding.EncodeToString([]byte("pat:" + pat))

	var gitUserName = os.Getenv("GIT_USER_NAME")
	if gitUserName == "" {
		gitUserName = "dagger-ci"
	}

	var gitUserEmail = os.Getenv("GIT_USER_EMAIL")
	if gitUserEmail == "" {
		gitUserEmail = "hellog@dagger.io"
	}

	git := util.GoBase(c).
		Exec(dagger.ContainerExecOpts{
			Args: []string{"apk", "add", "-U", "--no-cache", "git"},
		}).
		Exec(dagger.ContainerExecOpts{
			Args: []string{"git", "config", "--global", "user.name", gitUserName},
		}).
		Exec(dagger.ContainerExecOpts{
			Args: []string{"git", "config", "--global", "user.email", gitUserEmail},
		}).
		Exec(dagger.ContainerExecOpts{
			Args: []string{"git", "config", "--global",
				"http.https://github.com/.extraheader",
				fmt.Sprintf("AUTHORIZATION: Basic %s", encodedPAT),
			},
		})

	repository := git.Exec(dagger.ContainerExecOpts{
		Args: []string{"git", "clone", "https://github.com/dagger/dagger.git", "/src/dagger"},
	}).WithWorkdir("/src/dagger")

	filtered := repository.
		WithEnvVariable("FILTER_BRANCH_SQUELCH_WARNING", "1").
		Exec(dagger.ContainerExecOpts{
			Args: []string{
				"git", "filter-branch", "-f", "--prune-empty",
				"--subdirectory-filter", "sdk/go",
				"--tree-filter", "if [ -f go.mod ]; then go mod edit -dropreplace github.com/dagger/dagger; fi",
				"--", tag,
			},
		})

	// Push
	_, err = filtered.Exec(dagger.ContainerExecOpts{
		Args: []string{
			"git",
			"push",
			"-f",
			targetRepo,
			fmt.Sprintf("%s:%s", tag, targetTag),
		},
	}).ExitCode(ctx)

	return err
}

// Bump the Go SDK's Engine dependency
func (t Go) Bump(ctx context.Context, version string) error {
	versionFile := fmt.Sprintf(`// Code generated by dagger. DO NOT EDIT.

package dagger

const engineImageRef = %q
`, version)

	return os.WriteFile("sdk/go/engine.gen.go", []byte(versionFile), 0600)
}
