package repocli

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"

	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
	MediaTypeOIIv1  = "application/vnd.oci.image.index.v1+json"
	MediaTypeDDMLv2 = "application/vnd.docker.distribution.manifest.list.v2+json"

	MediaTypeDDMv2 = "application/vnd.docker.distribution.manifest.v2+json"
	MediaTypeOIMv1 = "application/vnd.oci.image.manifest.v1+json"
)

type Loader struct {
	cli *Client
}

func NewLoader(client *Client) *Loader {
	return &Loader{
		cli: client,
	}
}

func (down *Loader) Pull(ctx context.Context, rawref, dir, osname, arch string) error {
	var err error
	ref, err := NewReferer(rawref)
	if err != nil {
		return err
	}
	rawrepo := ref.RawRepo()
	tag := ref.Tag()

	exist, mime, man, err := down.cli.GetRawManifest(ctx, ref.Raw())
	if err != nil {
		return err
	}
	if !exist {
		err = errors.New("Manifest not found")
		return err
	}

	var digstr string
	if mime == MediaTypeOIIv1 || mime == MediaTypeDDMLv2 {
		var index ocispec.Index
		err = json.Unmarshal(man, &index)
		if err != nil {
			return err
		}
		for _, descr := range index.Manifests {
			if descr.Platform != nil {
				cond := descr.Platform.Architecture == arch
				cond = cond && descr.Platform.OS == osname
				if cond {
					tag = descr.Digest.String()
					digstr = descr.Digest.String()
				}
			}
		}
	}
	if digstr == "" {
		err = errors.New("Manifest for required arch and OS not found")
		return err
	}
	fmt.Printf("Tag: %s\n", tag)
	exist, mime, man, err = down.cli.GetRawManifest(ctx, ref.RawWithTag(digstr))
	if err != nil {
		return err
	}
	fmt.Printf("Mime: %s\n", mime)
	if !exist {
		err = errors.New("Manifest not found")
		return err
	}
	if mime != MediaTypeOIMv1 && mime != MediaTypeDDMv2 {
		err = errors.New("Unknown manifest media type")
		return err
	}
	var manifest ocispec.Manifest
	err = json.Unmarshal(man, &manifest)
	if err != nil {
		return err
	}
	// Write image manifest
	imager := NewEmptyImager(dir)
	err = imager.WriteManifest(ctx, digstr, mime, man)
	if err != nil {
		return err
	}

	layers := make([]ocispec.Descriptor, 0)
	layers = append(layers, manifest.Config)
	layers = append(layers, manifest.Layers...)
	for _, layer := range layers {
		wrapfunc := func() error {
			tmpfile, err := os.CreateTemp(dir, "*.bin")
			if err != nil {
				return err
			}
			defer tmpfile.Close()
			tmpname := tmpfile.Name()
			defer os.Remove(tmpname)

			digstr := layer.Digest.String()
			exist, err := down.cli.GetBlob(ctx, rawrepo, tmpfile, digstr)
			if err != nil {
				return err
			}
			if !exist {
				err = errors.New("Layer not found")
				return err
			}
			tmpfile.Seek(0, 0)
			size := layer.Size
			err = imager.WriteLayer(ctx, digstr, size, tmpfile)
			if err != nil {
				return err
			}
			return err
		}
		err = wrapfunc()
		if err != nil {
			return err
		}
	}
	return err
}
