package main

import (
	"context"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/containers/buildah"
	"github.com/containers/buildah/util"
	is "github.com/containers/image/storage"
	"github.com/containers/image/types"
	lu "github.com/containers/libpod/pkg/util"
	"github.com/containers/storage"
	digest "github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

var needToShutdownStore = false

func getStore(c *cli.Context) (storage.Store, error) {
	options, err := lu.GetDefaultStoreOptions()
	if err != nil {
		return nil, err
	}
	if c.GlobalIsSet("root") || c.GlobalIsSet("runroot") {
		options.GraphRoot = c.GlobalString("root")
		options.RunRoot = c.GlobalString("runroot")
	}
	if err := os.Setenv("XDG_RUNTIME_DIR", options.RunRoot); err != nil {
		return nil, errors.New("could not set XDG_RUNTIME_DIR")
	}

	if c.GlobalIsSet("storage-driver") {
		options.GraphDriverName = c.GlobalString("storage-driver")
		// If any options setup in config, these should be dropped if user overrode the driver
		options.GraphDriverOptions = []string{}
	}
	if c.GlobalIsSet("storage-opt") {
		opts := c.GlobalStringSlice("storage-opt")
		if len(opts) > 0 {
			options.GraphDriverOptions = opts
		}
	}
	if c.GlobalIsSet("userns-uid-map") && c.GlobalIsSet("userns-gid-map") {
		uopts := c.GlobalStringSlice("userns-uid-map")
		gopts := c.GlobalStringSlice("userns-gid-map")
		if len(uopts) == 0 {
			return nil, errors.New("--userns-uid-map used with no mappings?")
		}
		if len(gopts) == 0 {
			return nil, errors.New("--userns-gid-map used with no mappings?")
		}
		uidmap, gidmap, err := util.ParseIDMappings(uopts, gopts)
		if err != nil {
			return nil, err
		}
		options.UIDMap = uidmap
		options.GIDMap = gidmap
	} else if c.GlobalIsSet("userns-uid-map") {
		return nil, errors.Errorf("--userns-uid-map requires --userns-gid-map")
	} else if c.GlobalIsSet("userns-gid-map") {
		return nil, errors.Errorf("--userns-gid-map requires --userns-uid-map")
	}
	store, err := storage.GetStore(options)
	if store != nil {
		is.Transport.SetStore(store)
	}
	needToShutdownStore = true
	return store, err
}

func openBuilder(ctx context.Context, store storage.Store, name string) (builder *buildah.Builder, err error) {
	if name != "" {
		builder, err = buildah.OpenBuilder(store, name)
		if os.IsNotExist(err) {
			options := buildah.ImportOptions{
				Container: name,
			}
			builder, err = buildah.ImportBuilder(ctx, store, options)
		}
	}
	if err != nil {
		return nil, errors.Wrapf(err, "error reading build container")
	}
	if builder == nil {
		return nil, errors.Errorf("error finding build container")
	}
	return builder, nil
}

func openBuilders(store storage.Store) (builders []*buildah.Builder, err error) {
	return buildah.OpenAllBuilders(store)
}

func openImage(ctx context.Context, sc *types.SystemContext, store storage.Store, name string) (builder *buildah.Builder, err error) {
	options := buildah.ImportFromImageOptions{
		Image:         name,
		SystemContext: sc,
	}
	builder, err = buildah.ImportBuilderFromImage(ctx, store, options)
	if err != nil {
		return nil, errors.Wrapf(err, "error reading image")
	}
	if builder == nil {
		return nil, errors.Errorf("error mocking up build configuration")
	}
	return builder, nil
}

func getDateAndDigestAndSize(ctx context.Context, image storage.Image, store storage.Store) (time.Time, string, int64, error) {
	created := time.Time{}
	is.Transport.SetStore(store)
	storeRef, err := is.Transport.ParseStoreReference(store, image.ID)
	if err != nil {
		return created, "", -1, err
	}
	img, err := storeRef.NewImage(ctx, nil)
	if err != nil {
		return created, "", -1, err
	}
	defer img.Close()
	imgSize, sizeErr := img.Size()
	if sizeErr != nil {
		imgSize = -1
	}
	manifest, _, manifestErr := img.Manifest(ctx)
	manifestDigest := ""
	if manifestErr == nil && len(manifest) > 0 {
		manifestDigest = digest.Canonical.FromBytes(manifest).String()
	}
	inspectInfo, inspectErr := img.Inspect(ctx)
	if inspectErr == nil && inspectInfo != nil {
		created = *inspectInfo.Created
	}
	if sizeErr != nil {
		err = sizeErr
	} else if manifestErr != nil {
		err = manifestErr
	} else if inspectErr != nil {
		err = inspectErr
	}
	return created, manifestDigest, imgSize, err
}

// getContext returns a context.TODO
func getContext() context.Context {
	return context.TODO()
}

var userFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "user",
		Usage: "`user[:group]` to run the command as",
	},
}

func defaultFormat() string {
	format := os.Getenv("BUILDAH_FORMAT")
	if format != "" {
		return format
	}
	return buildah.OCI
}

// imageIsParent goes through the layers in the store and checks if i.TopLayer is
// the parent of any other layer in store. Double check that image with that
// layer exists as well.
func imageIsParent(store storage.Store, topLayer string) (bool, error) {
	children, err := getChildren(store, topLayer)
	if err != nil {
		return false, err
	}
	return len(children) > 0, nil
}

// getParent returns the image ID of the parent. Return nil if a parent is not found.
func getParent(store storage.Store, topLayer string) (*storage.Image, error) {
	images, err := store.Images()
	if err != nil {
		return nil, errors.Wrapf(err, "unable to retrieve images from store")
	}
	layer, err := store.Layer(topLayer)
	if err != nil {
		return nil, errors.Wrapf(err, "unable to retrieve layers from store")
	}
	for _, img := range images {
		if img.TopLayer == layer.Parent {
			return &img, nil
		}
	}
	return nil, nil
}

// getChildren returns a list of the imageIDs that depend on the image
func getChildren(store storage.Store, topLayer string) ([]string, error) {
	var children []string
	images, err := store.Images()
	if err != nil {
		return nil, errors.Wrapf(err, "unable to retrieve images from store")
	}
	layers, err := store.Layers()
	if err != nil {
		return nil, errors.Wrapf(err, "unable to retrieve layers from store")
	}

	for _, layer := range layers {
		if layer.Parent == topLayer {
			if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 {
				children = append(children, imageID...)
			}
		}
	}
	return children, nil
}

// getImageOfTopLayer returns the image ID where layer is the top layer of the image
func getImageOfTopLayer(images []storage.Image, layer string) []string {
	var matches []string
	for _, img := range images {
		if img.TopLayer == layer {
			matches = append(matches, img.ID)
		}
	}
	return matches
}

func getFormat(c *cli.Context) (string, error) {
	format := strings.ToLower(c.String("format"))
	switch format {
	case buildah.OCI:
		return buildah.OCIv1ImageManifest, nil
	case buildah.DOCKER:
		return buildah.Dockerv2ImageManifest, nil
	default:
		return "", errors.Errorf("unrecognized image type %q", format)
	}
}

func sortFlags(flags []cli.Flag) []cli.Flag {
	sort.Slice(flags, func(i, j int) bool {
		return strings.Compare(flags[i].GetName(), flags[j].GetName()) < 0
	})
	return flags
}
