package client

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"coopcloud.tech/abra/pkg/web"
	"github.com/docker/distribution/reference"
	"github.com/docker/docker/client"
	"github.com/hashicorp/go-retryablehttp"
	"github.com/sirupsen/logrus"
)

type RawTag struct {
	Layer string
	Name  string
}

type RawTags []RawTag

var registryURL = "https://registry.hub.docker.com/v1/repositories/%s/tags"

func GetRegistryTags(image string) (RawTags, error) {
	var tags RawTags

	tagsUrl := fmt.Sprintf(registryURL, image)
	if err := web.ReadJSON(tagsUrl, &tags); err != nil {
		return tags, err
	}

	return tags, nil
}

func basicAuth(username, password string) string {
	auth := username + ":" + password
	return base64.StdEncoding.EncodeToString([]byte(auth))
}

// getRegv2Token retrieves a registry v2 authentication token.
func getRegv2Token(cl *client.Client, image reference.Named, registryUsername, registryPassword string) (string, error) {
	img := reference.Path(image)
	tokenURL := "https://auth.docker.io/token"
	values := fmt.Sprintf("service=registry.docker.io&scope=repository:%s:pull", img)

	fullURL := fmt.Sprintf("%s?%s", tokenURL, values)
	req, err := retryablehttp.NewRequest("GET", fullURL, nil)
	if err != nil {
		return "", err
	}

	if registryUsername != "" && registryPassword != "" {
		logrus.Debugf("using registry log in credentials for token request")
		auth := basicAuth(registryUsername, registryPassword)
		req.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth))
	}

	client := web.NewHTTPRetryClient()
	res, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		_, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return "", err
		}
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", nil
	}

	tokenRes := struct {
		AccessToken string `json:"access_token"`
		Expiry      int    `json:"expires_in"`
		Issued      string `json:"issued_at"`
		Token       string `json:"token"`
	}{}

	if err := json.Unmarshal(body, &tokenRes); err != nil {
		return "", err
	}

	return tokenRes.Token, nil
}

// GetTagDigest retrieves an image digest from a v2 registry
func GetTagDigest(cl *client.Client, image reference.Named, registryUsername, registryPassword string) (string, error) {
	img := reference.Path(image)
	tag := image.(reference.NamedTagged).Tag()
	manifestURL := fmt.Sprintf("https://index.docker.io/v2/%s/manifests/%s", img, tag)

	req, err := retryablehttp.NewRequest("GET", manifestURL, nil)
	if err != nil {
		return "", err
	}

	token, err := getRegv2Token(cl, image, registryUsername, registryPassword)
	if err != nil {
		return "", err
	}

	if token == "" {
		return "", fmt.Errorf("unable to retrieve registry token?")
	}

	req.Header = http.Header{
		"Accept": []string{
			"application/vnd.docker.distribution.manifest.v2+json",
			"application/vnd.docker.distribution.manifest.list.v2+json",
		},
		"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
	}

	client := web.NewHTTPRetryClient()
	res, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		_, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return "", err
		}
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}

	registryResT1 := struct {
		SchemaVersion int
		MediaType     string
		Manifests     []struct {
			MediaType string
			Size      int
			Digest    string
			Platform  struct {
				Architecture string
				Os           string
			}
		}
	}{}

	registryResT2 := struct {
		SchemaVersion int
		MediaType     string
		Config        struct {
			MediaType string
			Size      int
			Digest    string
		}
		Layers []struct {
			MediaType string
			Size      int
			Digest    string
		}
	}{}

	if err := json.Unmarshal(body, &registryResT1); err != nil {
		return "", err
	}

	var digest string
	for _, manifest := range registryResT1.Manifests {
		if string(manifest.Platform.Architecture) == "amd64" {
			digest = strings.Split(manifest.Digest, ":")[1][:7]
		}
	}

	if digest == "" {
		if err := json.Unmarshal(body, &registryResT2); err != nil {
			return "", err
		}
		digest = strings.Split(registryResT2.Config.Digest, ":")[1][:7]
	}

	if digest == "" {
		return "", fmt.Errorf("Unable to retrieve amd64 digest for %s", image)
	}

	return digest, nil
}