package server

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/docker/distribution"
	"github.com/docker/distribution/context"
	"github.com/docker/distribution/manifest/schema2"

	imageapi "github.com/openshift/origin/pkg/image/api"
)

// Context keys
const (
	// repositoryKey serves to store/retrieve repository object to/from context.
	repositoryKey = "openshift.repository"
)

func WithRepository(parent context.Context, repo *repository) context.Context {
	return context.WithValue(parent, repositoryKey, repo)
}
func RepositoryFrom(ctx context.Context) (repo *repository, found bool) {
	repo, found = ctx.Value(repositoryKey).(*repository)
	return
}

func getOptionValue(
	envVar string,
	optionName string,
	defval interface{},
	options map[string]interface{},
	conversionFunc func(v interface{}) (interface{}, error),
) (value interface{}, err error) {
	value = defval
	if optValue, ok := options[optionName]; ok {
		converted, convErr := conversionFunc(optValue)
		if convErr != nil {
			err = fmt.Errorf("config option %q: invalid value: %v", optionName, convErr)
		} else {
			value = converted
		}
	}

	if len(envVar) == 0 {
		return
	}
	envValue := os.Getenv(envVar)
	if len(envValue) == 0 {
		return
	}

	converted, convErr := conversionFunc(envValue)
	if convErr != nil {
		err = fmt.Errorf("invalid value of environment variable %s: %v", envVar, convErr)
	} else {
		value = converted
	}

	return
}

func getBoolOption(envVar string, optionName string, defval bool, options map[string]interface{}) (bool, error) {
	value, err := getOptionValue(envVar, optionName, defval, options, func(value interface{}) (b interface{}, err error) {
		switch t := value.(type) {
		case bool:
			return t, nil
		case string:
			switch strings.ToLower(t) {
			case "true":
				return true, nil
			case "false":
				return false, nil
			}
		}
		return defval, fmt.Errorf("%#+v is not a boolean", value)
	})

	return value.(bool), err
}

func getDurationOption(envVar string, optionName string, defval time.Duration, options map[string]interface{}) (time.Duration, error) {
	value, err := getOptionValue(envVar, optionName, defval, options, func(value interface{}) (d interface{}, err error) {
		s, ok := value.(string)
		if !ok {
			return defval, fmt.Errorf("expected string, not %T", value)
		}

		parsed, err := time.ParseDuration(s)
		if err != nil {
			return defval, fmt.Errorf("parse duration error: %v", err)
		}
		return parsed, nil
	})

	return value.(time.Duration), err
}

// deserializedManifestFromImage converts an Image to a DeserializedManifest.
func deserializedManifestFromImage(image *imageapi.Image) (*schema2.DeserializedManifest, error) {
	var manifest schema2.DeserializedManifest
	if err := json.Unmarshal([]byte(image.DockerImageManifest), &manifest); err != nil {
		return nil, err
	}
	return &manifest, nil
}

func getNamespaceName(resourceName string) (string, string, error) {
	repoParts := strings.SplitN(resourceName, "/", 2)
	if len(repoParts) != 2 {
		return "", "", ErrNamespaceRequired
	}
	ns := repoParts[0]
	if len(ns) == 0 {
		return "", "", ErrNamespaceRequired
	}
	name := repoParts[1]
	if len(name) == 0 {
		return "", "", ErrNamespaceRequired
	}
	return ns, name, nil
}

// effectiveCreateOptions find out what the blob creation options are going to do by dry-running them.
func effectiveCreateOptions(options []distribution.BlobCreateOption) (*distribution.CreateOptions, error) {
	opts := &distribution.CreateOptions{}
	for _, createOptions := range options {
		err := createOptions.Apply(opts)
		if err != nil {
			return nil, err
		}
	}
	return opts, nil
}