package testutil

import (
	"context"
	"encoding/json"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/platforms"
	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

type ImageInfo struct {
	Desc         ocispecs.Descriptor
	Manifest     ocispecs.Manifest
	Img          ocispecs.Image
	Layers       []map[string]*TarItem
	LayersRaw    [][]byte
	descPlatform string
}

type ImagesInfo struct {
	Desc   ocispecs.Descriptor
	Index  ocispecs.Index
	Images []*ImageInfo
}

func (idx ImagesInfo) Find(platform string) *ImageInfo {
	result := idx.Filter(platform)
	if len(result.Images) == 0 {
		return nil
	}
	return result.Images[0]
}

func (idx ImagesInfo) Filter(platform string) *ImagesInfo {
	result := &ImagesInfo{Desc: idx.Desc}
	for _, info := range idx.Images {
		if info.descPlatform == platform {
			result.Images = append(result.Images, info)
		}
	}
	return result
}

func (idx ImagesInfo) FindAttestation(platform string) *ImageInfo {
	img := idx.Find(platform)
	if img == nil {
		return nil
	}
	for _, info := range idx.Images {
		if info.Desc.Annotations["vnd.docker.reference.digest"] == string(img.Desc.Digest) {
			return info
		}
	}
	return nil
}

func ReadImages(ctx context.Context, p content.Provider, desc ocispecs.Descriptor) (*ImagesInfo, error) {
	idx := &ImagesInfo{Desc: desc}

	dt, err := content.ReadBlob(ctx, p, desc)
	if err != nil {
		return nil, err
	}
	if err := json.Unmarshal(dt, &idx.Index); err != nil {
		return nil, err
	}
	if !images.IsIndexType(idx.Index.MediaType) {
		img, err := ReadImage(ctx, p, desc)
		if err != nil {
			return nil, err
		}
		img.descPlatform = platforms.Format(img.Img.Platform)
		idx.Images = append(idx.Images, img)
		return idx, nil
	}

	for _, m := range idx.Index.Manifests {
		img, err := ReadImage(ctx, p, m)
		if err != nil {
			return nil, err
		}
		img.descPlatform = platforms.Format(*m.Platform)
		idx.Images = append(idx.Images, img)
	}
	return idx, nil
}

func ReadImage(ctx context.Context, p content.Provider, desc ocispecs.Descriptor) (*ImageInfo, error) {
	ii := &ImageInfo{Desc: desc}

	dt, err := content.ReadBlob(ctx, p, desc)
	if err != nil {
		return nil, err
	}
	if err := json.Unmarshal(dt, &ii.Manifest); err != nil {
		return nil, err
	}
	if !images.IsManifestType(ii.Manifest.MediaType) {
		return nil, errors.Errorf("invalid manifest type %s", ii.Manifest.MediaType)
	}

	dt, err = content.ReadBlob(ctx, p, ii.Manifest.Config)
	if err != nil {
		return nil, err
	}
	if err := json.Unmarshal(dt, &ii.Img); err != nil {
		return nil, err
	}

	ii.Layers = make([]map[string]*TarItem, len(ii.Manifest.Layers))
	ii.LayersRaw = make([][]byte, len(ii.Manifest.Layers))
	for i, l := range ii.Manifest.Layers {
		dt, err := content.ReadBlob(ctx, p, l)
		if err != nil {
			return nil, err
		}
		ii.LayersRaw[i] = dt
		if images.IsLayerType(l.MediaType) {
			m, err := ReadTarToMap(dt, true)
			if err != nil {
				return nil, err
			}
			ii.Layers[i] = m
		}
	}
	return ii, nil
}