package sdk

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/dagger/dagger/internal/mage/util"
	"github.com/moby/buildkit/identity"

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

const (
	phpSDKPath         = "sdk/php"
	phpSDKGeneratedDir = "generated"
	phpSDKVersionFile  = "src/Connection/version.php"
)

var _ SDK = PHP{}

type PHP mg.Namespace

// Lint lints the PHP SDK
func (PHP) Lint(_ context.Context) error {
	// TODO
	return nil
}

// Test tests the PHP SDK
func (PHP) Test(_ context.Context) error {
	// TODO
	return nil
}

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

	c = c.Pipeline("sdk").Pipeline("php").Pipeline("generate")

	devEngine, endpoint, err := util.CIDevEngineContainerAndEndpoint(
		ctx,
		c.Pipeline("dev-engine"),
		util.DevEngineOpts{Name: "sdk-php-generate"},
	)
	if err != nil {
		return err
	}

	generatedPath := filepath.Join(phpSDKPath, phpSDKGeneratedDir)
	if err := os.RemoveAll(generatedPath); err != nil {
		return err
	}

	cliBinPath := "/.dagger-cli"
	ok, err := phpBase(c).
		WithServiceBinding("dagger-engine", devEngine).
		WithEnvVariable("_EXPERIMENTAL_DAGGER_RUNNER_HOST", endpoint).
		WithMountedFile(cliBinPath, util.DaggerBinary(c)).
		WithEnvVariable("_EXPERIMENTAL_DAGGER_CLI_BIN", cliBinPath).
		With(util.ShellCmds(
			fmt.Sprintf("rm -f %s/*.php", phpSDKGeneratedDir),
			fmt.Sprintf("%s run ./codegen", cliBinPath),
		)).
		Directory(phpSDKGeneratedDir).
		Export(ctx, generatedPath)
	if err != nil {
		return err
	}
	if !ok {
		return fmt.Errorf("cannot export generated code to %q", generatedPath)
	}
	return nil
}

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

	c = c.Pipeline("sdk").Pipeline("php").Pipeline("publish")

	dryRun, _ := strconv.ParseBool(os.Getenv("DRY_RUN"))

	var targetTag = strings.TrimPrefix(tag, phpSDKPath+"/")

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

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

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

	git := util.GoBase(c).
		WithExec([]string{"apk", "add", "-U", "--no-cache", "git"}).
		WithExec([]string{"git", "config", "--global", "user.name", gitUserName}).
		WithExec([]string{"git", "config", "--global", "user.email", gitUserEmail})
	if !dryRun {
		pat := util.GetHostEnv("GITHUB_PAT")
		encodedPAT := base64.URLEncoding.EncodeToString([]byte("pat:" + pat))
		git = git.
			WithEnvVariable("GIT_CONFIG_COUNT", "1").
			WithEnvVariable("GIT_CONFIG_KEY_0", "http.https://github.com/.extraheader").
			WithSecretVariable("GIT_CONFIG_VALUE_0", c.SetSecret("GITHUB_HEADER", fmt.Sprintf("AUTHORIZATION: Basic %s", encodedPAT)))
	}

	result := git.
		WithEnvVariable("CACHEBUSTER", identity.NewID()).
		WithExec([]string{"git", "clone", "https://github.com/dagger/dagger.git", "/src/dagger"}).
		WithWorkdir("/src/dagger").
		WithEnvVariable("FILTER_BRANCH_SQUELCH_WARNING", "1").
		WithExec([]string{
			"git", "filter-branch", "-f", "--prune-empty",
			"--subdirectory-filter", phpSDKPath,
			"--", tag,
		})
	if !dryRun {
		result = result.WithExec([]string{
			"git",
			"push",
			"-f",
			targetRepo,
			fmt.Sprintf("%s:%s", tag, targetTag),
		})
	}
	_, err = result.Sync(ctx)
	return err
}

// Bump the PHP SDK's Engine dependency
func (PHP) Bump(_ context.Context, version string) error {
	// trim leading v from version
	version = strings.TrimPrefix(version, "v")
	content := fmt.Sprintf(`<?php /* Code generated by dagger. DO NOT EDIT. */ return '%s';
`, version)

	return os.WriteFile(filepath.Join(phpSDKPath, phpSDKVersionFile), []byte(content), 0o600)
}

// phpBase returns a PHP container with the PHP SDK source files
// added and dependencies installed.
func phpBase(c *dagger.Client) *dagger.Container {
	src := util.Repository(c).Directory(phpSDKPath)

	return c.Container().
		From("php:8.2-zts-bookworm").
		WithExec([]string{"apt-get", "update"}).
		WithExec([]string{"apt-get", "install", "-y", "git", "unzip"}).
		WithFile("/usr/bin/composer", c.Container().
			From("composer:2").
			File("/usr/bin/composer"),
		).
		WithMountedCache("/root/.composer", c.CacheVolume("composer-cache-8.2-zts-bookworm")).
		WithEnvVariable("COMPOSER_HOME", "/root/.composer").
		WithEnvVariable("COMPOSER_ALLOW_SUPERUSER", "1").
		WithWorkdir(fmt.Sprintf("/%s", phpSDKPath)).
		WithFile("composer.json", src.File("composer.json")).
		WithFile("composer.lock", src.File("composer.lock")).
		WithExec([]string{"composer", "install"}).
		WithDirectory(".", src)
}
