package executables

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/aws/eks-anywhere/pkg/logger"
)

// Temporary: Curated packages dev and prod accounts are currently hard coded
// This is because there is no mechanism to extract these values as of now.
const (
	dockerPath        = "docker"
	defaultRegistry   = "public.ecr.aws"
	packageProdDomain = "783794618700.dkr.ecr.us-west-2.amazonaws.com"
	packageDevDomain  = "067575901363.dkr.ecr.us-west-2.amazonaws.com"
)

type Docker struct {
	Executable
}

func NewDocker(executable Executable) *Docker {
	return &Docker{Executable: executable}
}

func (d *Docker) GetDockerLBPort(ctx context.Context, clusterName string) (port string, err error) {
	clusterLBName := fmt.Sprintf("%s-lb", clusterName)
	if stdout, err := d.Execute(ctx, "port", clusterLBName, "6443/tcp"); err != nil {
		return "", err
	} else {
		return strings.Split(stdout.String(), ":")[1], nil
	}
}

func (d *Docker) PullImage(ctx context.Context, image string) error {
	logger.V(2).Info("Pulling docker image", "image", image)
	if _, err := d.Execute(ctx, "pull", image); err != nil {
		return err
	} else {
		return nil
	}
}

func (d *Docker) Version(ctx context.Context) (int, error) {
	cmdOutput, err := d.Execute(ctx, "version", "--format", "{{.Client.Version}}")
	if err != nil {
		return 0, fmt.Errorf("please check if docker is installed and running %v", err)
	}
	dockerVersion := strings.TrimSpace(cmdOutput.String())
	versionSplit := strings.Split(dockerVersion, ".")
	installedMajorVersion := versionSplit[0]
	installedMajorVersionInt, err := strconv.Atoi(installedMajorVersion)
	if err != nil {
		return 0, err
	}
	return installedMajorVersionInt, nil
}

func (d *Docker) AllocatedMemory(ctx context.Context) (uint64, error) {
	cmdOutput, err := d.Execute(ctx, "info", "--format", "'{{json .MemTotal}}'")
	if err != nil {
		return 0, fmt.Errorf("please check if docker is installed and running %v", err)
	}
	totalMemory := cmdOutput.String()
	totalMemory = totalMemory[1 : len(totalMemory)-2]
	return strconv.ParseUint(totalMemory, 10, 64)
}

func (d *Docker) TagImage(ctx context.Context, image string, endpoint string) error {
	replacer := strings.NewReplacer(defaultRegistry, endpoint, packageProdDomain, endpoint, packageDevDomain, endpoint)
	localImage := replacer.Replace(image)
	logger.Info("Tagging image", "image", image, "local image", localImage)
	if _, err := d.Execute(ctx, "tag", image, localImage); err != nil {
		return err
	}
	return nil
}

func (d *Docker) PushImage(ctx context.Context, image string, endpoint string) error {
	replacer := strings.NewReplacer(defaultRegistry, endpoint, packageProdDomain, endpoint, packageDevDomain, endpoint)
	localImage := replacer.Replace(image)
	logger.Info("Pushing", "image", localImage)
	if _, err := d.Execute(ctx, "push", localImage); err != nil {
		return err
	}
	return nil
}

func (d *Docker) Login(ctx context.Context, endpoint, username, password string) error {
	params := []string{"login", endpoint, "--username", username, "--password-stdin"}
	logger.Info(fmt.Sprintf("Logging in to docker registry %s", endpoint))
	_, err := d.ExecuteWithStdin(ctx, []byte(password), params...)
	return err
}

func (d *Docker) LoadFromFile(ctx context.Context, filepath string) error {
	if _, err := d.Execute(ctx, "load", "-i", filepath); err != nil {
		return fmt.Errorf("loading images from file: %v", err)
	}

	return nil
}

func (d *Docker) SaveToFile(ctx context.Context, filepath string, images ...string) error {
	params := make([]string, 0, 3+len(images))
	params = append(params, "save", "-o", filepath)
	params = append(params, images...)

	if _, err := d.Execute(ctx, params...); err != nil {
		return fmt.Errorf("saving images to file: %v", err)
	}

	return nil
}

func (d *Docker) Run(ctx context.Context, image string, name string, cmd []string, flags ...string) error {
	params := []string{"run", "-d", "-i"}
	params = append(params, flags...)
	params = append(params, "--name", name, image)
	params = append(params, cmd...)

	if _, err := d.Execute(ctx, params...); err != nil {
		return fmt.Errorf("running docker container %s with image %s: %v", name, image, err)
	}
	return nil
}

func (d *Docker) ForceRemove(ctx context.Context, name string) error {
	params := []string{"rm", "-f", name}

	if _, err := d.Execute(ctx, params...); err != nil {
		return fmt.Errorf("force removing docker container %s: %v", name, err)
	}
	return nil
}

// CheckContainerExistence checks whether a Docker container with the provided name exists
// It returns true if a container with the name exists, false if it doesn't and an error if it encounters some other error.
func (d *Docker) CheckContainerExistence(ctx context.Context, name string) (bool, error) {
	params := []string{"container", "inspect", name}

	_, err := d.Execute(ctx, params...)
	if err == nil {
		return true, nil
	} else if strings.Contains(err.Error(), "No such container") {
		return false, nil
	}

	return false, fmt.Errorf("checking if a docker container with name %s exists: %v", name, err)
}
