package imageutil

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"strings"
	"time"

	"github.com/containerd/containerd/remotes"
	digest "github.com/opencontainers/go-digest"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

func readSchema1Config(ctx context.Context, ref string, desc specs.Descriptor, fetcher remotes.Fetcher, cache ContentCache) (digest.Digest, []byte, error) {
	rc, err := fetcher.Fetch(ctx, desc)
	if err != nil {
		return "", nil, err
	}
	defer rc.Close()
	dt, err := ioutil.ReadAll(rc)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to fetch schema1 manifest")
	}
	dt, err = convertSchema1ConfigMeta(dt)
	if err != nil {
		return "", nil, err
	}
	return desc.Digest, dt, nil
}

func convertSchema1ConfigMeta(in []byte) ([]byte, error) {
	type history struct {
		V1Compatibility string `json:"v1Compatibility"`
	}
	var m struct {
		History []history `json:"history"`
	}
	if err := json.Unmarshal(in, &m); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal schema1 manifest")
	}
	if len(m.History) == 0 {
		return nil, errors.Errorf("invalid schema1 manifest")
	}

	var img specs.Image
	if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), &img); err != nil {
		return nil, errors.Wrap(err, "failed to unmarshal image from schema 1 history")
	}

	img.RootFS = specs.RootFS{
		Type: "layers", // filled in by exporter
	}
	img.History = make([]specs.History, len(m.History))

	for i := range m.History {
		var h v1History
		if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &h); err != nil {
			return nil, errors.Wrap(err, "failed to unmarshal history")
		}
		img.History[len(m.History)-i-1] = specs.History{
			Author:     h.Author,
			Comment:    h.Comment,
			Created:    &h.Created,
			CreatedBy:  strings.Join(h.ContainerConfig.Cmd, " "),
			EmptyLayer: (h.ThrowAway != nil && *h.ThrowAway) || (h.Size != nil && *h.Size == 0),
		}
	}

	dt, err := json.MarshalIndent(img, "", "   ")
	if err != nil {
		return nil, errors.Wrap(err, "failed to marshal schema1 config")
	}
	return dt, nil
}

type v1History struct {
	Author          string    `json:"author,omitempty"`
	Created         time.Time `json:"created"`
	Comment         string    `json:"comment,omitempty"`
	ThrowAway       *bool     `json:"throwaway,omitempty"`
	Size            *int      `json:"Size,omitempty"` // used before ThrowAway field
	ContainerConfig struct {
		Cmd []string `json:"Cmd,omitempty"`
	} `json:"container_config,omitempty"`
}
