package main

import (
	"fmt"
	"strings"

	semver "github.com/Masterminds/semver/v3"
	"github.com/magefile/mage/sh"
	"github.com/pkg/errors"
)

const (
	DOCKER_VERSION_CONSTRAINT         = ">= 20.10.10"
	DOCKER_COMPOSE_VERSION_CONSTRAINT = ">= 1.29.2"
	DOCKER_BUILDX_VERSION_CONSTRAINT  = ">= 0.6.3"
)

func dockerBinary() string {
	return binaryWithExt("docker")
}

func dockerOutput(args ...string) (string, error) {
	return sh.Output(dockerBinary(), args...)
}

func dockerRun(args ...string) error {
	return sh.Run(dockerBinary(), args...)
}

type DockerBuildConfig struct {
	Platform          string
	BaseImageOverride string
}

func NewDockerBuildConfig(baseImageOverride string) DockerBuildConfig {
	config := DockerBuildConfig{}
	config.BaseImageOverride = baseImageOverride
	config.Platform = "x86_64"
	return config
}

func dockerBuildImage(config DockerBuildConfig, imageTag string, dockerFile string) error {
	dockerArgs := []string{
		"buildx", "build", "-o", "type=docker", "-t", imageTag, "-f", dockerFile,
	}
	if config.BaseImageOverride != "" {
		dockerArgs = append(dockerArgs, "--build-arg", fmt.Sprintf("BASE_IMAGE=%s", config.BaseImageOverride))
	}
	if config.Platform != "" {
		dockerArgs = append(dockerArgs, "--build-arg", fmt.Sprintf("PLATFORM=%s", config.Platform))
	}

	dockerArgs = append(dockerArgs, ".")
	err := dockerRun(dockerArgs...)
	if err != nil {
		return fmt.Errorf("failed to build %s from %s: %w", imageTag, dockerFile, err)
	}

	return nil
}

func dockerBuildxVersion() (*semver.Version, error) {
	output, err := dockerOutput("buildx", "version")
	if err != nil {
		return nil, errors.Errorf("error running version cmd: %v", err)
	}
	fields := strings.Fields(string(output))
	if len(fields) < 3 {
		return nil, errors.Errorf("unexpected version cmd output: %s", output)
	}
	version, err := semver.NewVersion(fields[1])
	if err != nil {
		return nil, errors.Errorf("error parsing version: %v", err)
	}
	return version, nil
}

func dockerComposeVersion() (*semver.Version, error) {
	output, err := dockerOutput("compose", "version")
	if err != nil {
		return nil, errors.Errorf("error running version cmd: %v", err)
	}
	fields := strings.Fields(string(output))
	if len(fields) < 3 {
		return nil, errors.Errorf("unexpected version cmd output: %s", output)
	}
	version, err := semver.NewVersion(fields[3])
	if err != nil {
		return nil, errors.Errorf("error parsing version: %v", err)
	}
	return version, nil
}

func dockerVersion() (*semver.Version, error) {
	output, err := dockerOutput("--version")
	if err != nil {
		return nil, errors.Errorf("error running version cmd: %v", err)
	}
	fields := strings.Fields(string(output))
	if len(fields) < 3 {
		return nil, errors.Errorf("unexpected version cmd output: %s", output)
	}
	version, err := semver.NewVersion(strings.Trim(fields[2], ","))
	if err != nil {
		return nil, errors.Errorf("error parsing version: %v", err)
	}
	return version, nil
}

func constraintCheck(version *semver.Version, versionRequirement string, dependencyName string) error {
	constraint, err := semver.NewConstraint(versionRequirement)
	if err != nil {
		return errors.Errorf("error parsing constraint: %v", err)
	}
	if !constraint.Check(version) {
		return errors.Errorf("found %s version %v but it failed constraint %v", dependencyName, version, constraint)
	}
	return nil
}

func dockerComposeCheck() error {
	version, err := dockerComposeVersion()
	if err != nil {
		return errors.Errorf("error getting version: %v", err)
	}
	return constraintCheck(version, DOCKER_COMPOSE_VERSION_CONSTRAINT, "docker-compose")
}

func dockerBuildxCheck() error {
	version, err := dockerBuildxVersion()
	if err != nil {
		return errors.Errorf("error getting version: %v", err)
	}
	return constraintCheck(version, DOCKER_BUILDX_VERSION_CONSTRAINT, "docker-buildx")
}

func dockerCheck() error {
	version, err := dockerVersion()
	if err != nil {
		return errors.Errorf("error getting version: %v", err)
	}
	constraint, err := semver.NewConstraint(DOCKER_VERSION_CONSTRAINT)
	if err != nil {
		return errors.Errorf("error parsing constraint: %v", err)
	}
	if !constraint.Check(version) {
		return errors.Errorf("found docker version %v but it failed constraint %v", version, constraint)
	}
	return nil
}
