// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package registry

import (
	"encoding/base64"
	"fmt"
	"strings"

	"github.com/google/go-containerregistry/pkg/name"
	"github.com/sorintlab/errors"
)

//func registryAuthToken(auth *types.DockerRegistryAuth) (string, error) {
//	if auth == nil {
//		return "", nil
//	}
//
//	switch auth.Type {
//	case types.DockerRegistryAuthTypeBasic:
//		authConfig := dtypes.AuthConfig{
//			Username: auth.Username,
//			Password: auth.Password,
//		}
//		authConfigj, err := json.Marshal(authConfig)
//		if err != nil {
//			panic(err)
//		}
//		return base64.URLEncoding.EncodeToString(authConfigj), nil
//
//	default:
//		return "", errors.Errorf("unsupported registry auth type %q", auth.Type)
//	}
//}

type DockerRegistryAuthType string

const (
	DockerRegistryAuthTypeBasic       DockerRegistryAuthType = "basic"
	DockerRegistryAuthTypeEncodedAuth DockerRegistryAuthType = "encodedauth"
)

type DockerRegistryAuth struct {
	Type     DockerRegistryAuthType
	Username string
	Password string
	Auth     string
}

// Docker config represents the docker config.json format. We only consider the "auths" part
type DockerConfig struct {
	Auths map[string]DockerConfigAuth `json:"auths,omitempty"`
}

// Docker config represents the docker config.json auth part. We only consider the "auth" token part
type DockerConfigAuth struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
	Auth     string `json:"auth,omitempty"`
}

// There are a variety of ways a domain may get qualified within the Docker credential file.
// We enumerate them here as format strings.
var (
	domainForms = []string{
		// Allow naked domains
		"%s",
		// Allow scheme-prefixed.
		"https://%s",
		"http://%s",
		// Allow scheme-prefixes with version in url path.
		"https://%s/v1/",
		"http://%s/v1/",
		"https://%s/v2/",
		"http://%s/v2/",
	}
)

func GetImageTagOrDigest(image string) (string, error) {
	ref, err := name.ParseReference(image, name.WeakValidation)
	if err != nil {
		return "", errors.WithStack(err)
	}
	return ref.Identifier(), nil
}

func GetRegistry(image string) (string, error) {
	ref, err := name.ParseReference(image, name.WeakValidation)
	if err != nil {
		return "", errors.WithStack(err)
	}
	regName := ref.Context().RegistryStr()
	return regName, nil
}

// ResolveAuth resolves the auth username and password for the provided registry name
func ResolveAuth(auths map[string]DockerRegistryAuth, regname string) (string, string, error) {
	if auths != nil {
		for _, form := range domainForms {
			if auth, ok := auths[fmt.Sprintf(form, regname)]; ok {
				switch auth.Type {
				case DockerRegistryAuthTypeEncodedAuth:
					decoded, err := base64.StdEncoding.DecodeString(auth.Auth)
					if err != nil {
						return "", "", errors.Wrapf(err, "failed to decode docker auth")
					}
					parts := strings.Split(string(decoded), ":")
					if len(parts) != 2 {
						return "", "", errors.Wrapf(err, "wrong docker auth")
					}
					return parts[0], parts[1], nil
				case DockerRegistryAuthTypeBasic:
					return auth.Username, auth.Password, nil
				default:
					return "", "", errors.Errorf("unsupported auth type %q", auth.Type)
				}
			}
		}
	}

	return "", "", nil
}

func GenDockerConfig(auths map[string]DockerRegistryAuth, images []string) (*DockerConfig, error) {
	dockerConfig := &DockerConfig{Auths: make(map[string]DockerConfigAuth)}
	for _, image := range images {
		ref, err := name.ParseReference(image, name.WeakValidation)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		regName := ref.Context().RegistryStr()

		if _, ok := dockerConfig.Auths[regName]; ok {
			continue
		}

		username, password, err := ResolveAuth(auths, regName)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to resolve auth")
		}
		delimited := fmt.Sprintf("%s:%s", username, password)
		auth := base64.StdEncoding.EncodeToString([]byte(delimited))
		dockerConfig.Auths[regName] = DockerConfigAuth{Username: username, Password: password, Auth: auth}
	}

	return dockerConfig, nil
}
