package docker

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/url"
	"os"
	"os/exec"

	"github.com/docker/docker/api/types/image"
	apiregistry "github.com/docker/docker/api/types/registry"
	"github.com/docker/docker/client"
)

type RegistryConfig struct {
	URL      *url.URL
	Username string
	Password string
}

func (c RegistryConfig) GetAuthConfig() apiregistry.AuthConfig {
	return apiregistry.AuthConfig{
		Username:      c.Username,
		Password:      c.Password,
		ServerAddress: c.URL.Host,
	}
}

func (c RegistryConfig) GetRegistryAuth() (string, error) {
	authConfig := apiregistry.AuthConfig{
		Username: c.Username,
		Password: c.Password,
	}
	encodedJSON, err := json.Marshal(authConfig)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(encodedJSON), nil
}

func (c RegistryConfig) GetBasicAuthorization() string {
	return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.Username, c.Password))))
}

type Docker struct {
	cli *client.Client
}

func New() (Docker, error) {
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return Docker{}, err
	}
	return Docker{
		cli: cli,
	}, nil
}

func (d Docker) Login(conf RegistryConfig) error {
	auth := conf.GetAuthConfig()
	return exec.Command("docker", "login", "-u", auth.Username, "-p", auth.Password, auth.ServerAddress).Run()
}

func (d Docker) Logout(conf RegistryConfig) error {
	auth := conf.GetAuthConfig()
	return exec.Command("docker", "logout", auth.ServerAddress).Run()
}

// ReplicateImage tags the given imagePath and pushes it to the given dest registry.
func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, dest RegistryConfig) error {
	// remove existing Image if any
	_, _ = d.cli.ImageRemove(ctx, imageRef, image.RemoveOptions{
		Force:         true,
		PruneChildren: true,
	})

	testfile, err := os.Open(imagePath)
	if err != nil {
		return err
	}
	defer testfile.Close()

	// load image into docker engine
	resp, err := d.cli.ImageLoad(ctx, testfile, true)
	if err != nil {
		return err
	}
	if _, err := io.Copy(io.Discard, resp.Body); err != nil {
		return err
	}
	defer resp.Body.Close()

	targetImageRef := fmt.Sprintf("%s/%s", dest.URL.Host, imageRef)

	if err = d.cli.ImageTag(ctx, imageRef, targetImageRef); err != nil {
		return err
	}
	defer func() {
		_, _ = d.cli.ImageRemove(ctx, imageRef, image.RemoveOptions{
			Force:         true,
			PruneChildren: true,
		})
		_, _ = d.cli.ImageRemove(ctx, targetImageRef, image.RemoveOptions{
			Force:         true,
			PruneChildren: true,
		})
	}()

	auth, err := dest.GetRegistryAuth()
	if err != nil {
		return err
	}

	pushOut, err := d.cli.ImagePush(ctx, targetImageRef, image.PushOptions{
		RegistryAuth: auth,
	})
	if err != nil {
		return err
	}
	defer pushOut.Close()

	if _, err = io.Copy(io.Discard, pushOut); err != nil {
		return err
	}
	return nil
}
