package client

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

	"coopcloud.tech/abra/pkg/web"
	"github.com/docker/distribution/reference"
)

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
}

// getRegv2Token retrieves a registry v2 authentication token.
func getRegv2Token(image reference.Named) (string, error) {
	img := reference.Path(image)
	authTokenURL := fmt.Sprintf("https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull", img)
	req, err := http.NewRequest("GET", authTokenURL, nil)
	if err != nil {
		return "", err
	}

	client := &http.Client{Timeout: web.Timeout}
	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 {
		Token  string
		Expiry string
		Issued string
	}{}

	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(image reference.Named) (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 := http.NewRequest("GET", manifestURL, nil)
	if err != nil {
		return "", err
	}

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

	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 := &http.Client{Timeout: web.Timeout}
	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
}