package docker

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/grafana/grafana/pkg/build/config"
)

// verifyArchive verifies the integrity of an archive file.
func verifyArchive(archive string) error {
	log.Printf("Verifying checksum of %q", archive)

	//nolint:gosec
	shaB, err := os.ReadFile(archive + ".sha256")
	if err != nil {
		return err
	}

	exp := strings.TrimSpace(string(shaB))

	//nolint:gosec
	f, err := os.Open(archive)
	if err != nil {
		return err
	}

	defer func() {
		if err := f.Close(); err != nil {
			log.Println("error closing file:", err)
		}
	}()

	h := sha256.New()
	_, err = io.Copy(h, f)
	if err != nil {
		return err
	}

	chksum := hex.EncodeToString(h.Sum(nil))
	if chksum != exp {
		return fmt.Errorf("archive checksum is different than expected: %q", archive)
	}

	log.Printf("Archive %q has expected checksum: %s", archive, exp)

	return nil
}

// BuildImage builds a Docker image.
// The image tag is returned.
func BuildImage(version string, arch config.Architecture, grafanaDir string, useUbuntu, shouldSave bool, edition config.Edition) ([]string, error) {
	var baseArch string

	switch arch {
	case "amd64":
	case "armv7":
		baseArch = "arm32v7/"
	case "arm64":
		baseArch = "arm64v8/"
	default:
		return []string{}, fmt.Errorf("unrecognized architecture %q", arch)
	}

	libc := "-musl"
	dockerfile := "Dockerfile"
	baseImage := fmt.Sprintf("%salpine:3.15", baseArch)
	tagSuffix := ""
	if useUbuntu {
		libc = ""
		dockerfile = "ubuntu.Dockerfile"
		baseImage = fmt.Sprintf("%subuntu:20.04", baseArch)
		tagSuffix = "-ubuntu"
	}

	var editionStr string
	var dockerRepo string
	var additionalDockerRepo string
	var tags []string
	var imageFileBase string
	switch edition {
	case config.EditionOSS:
		dockerRepo = "grafana/grafana-image-tags"
		additionalDockerRepo = "grafana/grafana-oss-image-tags"
		imageFileBase = "grafana-oss"
	case config.EditionEnterprise:
		dockerRepo = "grafana/grafana-enterprise-image-tags"
		imageFileBase = "grafana-enterprise"
		editionStr = "-enterprise"
	default:
		return []string{}, fmt.Errorf("unrecognized edition %s", edition)
	}

	buildDir := filepath.Join(grafanaDir, "packaging/docker")
	// For example: grafana-8.5.0-52819pre.linux-amd64-musl.tar.gz
	archive := fmt.Sprintf("grafana%s-%s.linux-%s%s.tar.gz", editionStr, version, arch, libc)
	if err := verifyArchive(filepath.Join(buildDir, archive)); err != nil {
		return []string{}, err
	}

	tag := fmt.Sprintf("%s:%s%s-%s", dockerRepo, version, tagSuffix, arch)
	tags = append(tags, tag)

	args := []string{
		"build", "--build-arg", fmt.Sprintf("BASE_IMAGE=%s", baseImage),
		"--build-arg", fmt.Sprintf("GRAFANA_TGZ=%s", archive), "--tag", tag, "--no-cache",
		"-f", dockerfile, ".",
	}

	log.Printf("Running Docker: docker %s", strings.Join(args, " "))
	//nolint:gosec
	cmd := exec.Command("docker", args...)
	cmd.Dir = buildDir
	cmd.Env = append(os.Environ(), "DOCKER_CLI_EXPERIMENTAL=enabled")
	if output, err := cmd.CombinedOutput(); err != nil {
		return []string{}, fmt.Errorf("building Docker image failed: %w\n%s", err, output)
	}
	if shouldSave {
		imageFile := fmt.Sprintf("%s-%s%s-%s.img", imageFileBase, version, tagSuffix, arch)
		//nolint:gosec
		cmd = exec.Command("docker", "save", tag, "-o", imageFile)
		cmd.Dir = buildDir
		if output, err := cmd.CombinedOutput(); err != nil {
			return []string{}, fmt.Errorf("saving Docker image failed: %w\n%s", err, output)
		}
		gcsURL := fmt.Sprintf("gs://grafana-prerelease/artifacts/docker/%s/%s", version, imageFile)
		//nolint:gosec
		cmd = exec.Command("gsutil", "cp", imageFile, gcsURL)
		cmd.Dir = buildDir
		if output, err := cmd.CombinedOutput(); err != nil {
			return []string{}, fmt.Errorf("storing Docker image failed: %w\n%s", err, output)
		}
		log.Printf("Docker image %s stored to grafana-prerelease GCS bucket", imageFile)
	}
	if additionalDockerRepo != "" {
		additionalTag := fmt.Sprintf("%s:%s%s-%s", additionalDockerRepo, version, tagSuffix, arch)

		//nolint:gosec
		cmd = exec.Command("docker", "tag", tag, additionalTag)
		cmd.Dir = buildDir
		if output, err := cmd.CombinedOutput(); err != nil {
			return []string{}, fmt.Errorf("tagging Docker image failed: %w\n%s", err, output)
		}
		tags = append(tags, additionalTag)
	}

	return tags, nil
}
