/*
  * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 *
 *
*/

package repocli

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

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

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, variant string) error {
	var err error
	if osname == "" {
		osname = runtime.GOOS
	}
	if arch == "" {
		arch = runtime.GOARCH
	}

	ref, err := NewReferer(rawref)
	if err != nil {
		return err
	}
	rawrepo := ref.RawRepo()
	accepts := []string{
		MediaTypeDDMLv2,
		MediaTypeDDMv2,
		MediaTypeOIMv1,
		MediaTypeOIIv1,
	}
	exist, mime, man, digstr, err := down.cli.GetRawManifest(ctx, ref.Raw(), accepts)
	if err != nil {
		return err
	}
	if !exist {
		err = errors.New("Manifest not found")
		return err
	}
	tag := ref.Tag()
	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 {
				var cond bool
				cond = descr.Platform.Architecture == arch
				cond = cond && descr.Platform.OS == osname
				if variant != "" {
					cond = cond && descr.Platform.Variant == variant
				}
				if cond {
					tag = descr.Digest.String()
					digstr = descr.Digest.String()
					break
				}
			}
		}
		if tag == "" {
			err = errors.New("Manifest for required arch and OS not found")
			return err
		}
		accepts := []string{
			MediaTypeDDMv2,
			MediaTypeOIMv1,
		}
		_, mime, man, digstr, err = down.cli.GetRawManifest(ctx, ref.RawWithTag(tag), accepts)
		if err != nil {
			return err
		}
	} else 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
}
