package controller

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"nf-repo/internal/interfaces"
	"nf-repo/internal/model"
	"nf-repo/internal/opt"

	"github.com/google/go-containerregistry/pkg/name"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/remote"
)

func PullRepo(ctx context.Context, repo string, tag string, proxy string,
	mh interfaces.ManifestHandler, bh interfaces.BlobHandler,
) (*v1.Manifest, error) {
	if repo == "" || tag == "" {
		return nil, fmt.Errorf("invalid repo or tag")
	}

	var (
		err       error
		transport = &http.Transport{}
		puller    *remote.Puller
		tn        name.Tag
		des       *remote.Descriptor
		img       v1.Image
		manifest  *v1.Manifest
		bs        []byte
		mhash     model.Hash
		target    = fmt.Sprintf("%s:%s", repo, tag)
	)

	if opt.Proxy != nil {
		transport.Proxy = http.ProxyURL(opt.Proxy)
	}

	if proxy != "" {
		var pu *url.URL
		if pu, err = url.Parse(proxy); err != nil {
			return nil, err
		}

		transport.Proxy = http.ProxyURL(pu)
	}

	if puller, err = remote.NewPuller(
		remote.WithTransport(transport),
	); err != nil {
		return nil, err
	}

	if tn, err = name.NewTag(target); err != nil {
		return nil, err
	}

	if des, err = puller.Get(ctx, tn); err != nil {
		return nil, err
	}

	if img, err = des.Image(); err != nil {
		return nil, err
	}

	if manifest, err = img.Manifest(); err != nil {
		return nil, err
	}

	total := model.ManifestCountSize(manifest)
	size := 0
	_ = total

	var (
		tly     v1.Layer
		tdigest v1.Hash
		treader io.ReadCloser
	)

	if tly, err = img.LayerByDigest(manifest.Config.Digest); err != nil {
		return nil, err
	}

	if tdigest, err = tly.Digest(); err != nil {
		return nil, err
	}

	if treader, err = tly.Uncompressed(); err != nil {
		return nil, err
	}
	defer treader.Close()

	if err = bh.Put(
		ctx,
		target,
		model.Hash{Algorithm: tdigest.Algorithm, Hex: tdigest.Hex},
		treader,
	); err != nil {
		return nil, err
	}

	size = size + int(manifest.Config.Size)

	if bs, err = json.Marshal(manifest); err != nil {
		return nil, err
	}

	if mhash, _, err = model.SHA256(bytes.NewReader(bs)); err != nil {
		return nil, err
	}

	for idx := range manifest.Layers {
		var (
			reader io.ReadCloser
			lyHash v1.Hash
			ly     v1.Layer
		)

		lyHash = manifest.Layers[idx].Digest

		if ly, err = img.LayerByDigest(manifest.Layers[idx].Digest); err != nil {
			return nil, err
		}

		if reader, err = ly.Compressed(); err != nil {
			return nil, err
		}
		defer reader.Close()

		if err = bh.Put(ctx, repo, model.Hash{Algorithm: lyHash.Algorithm, Hex: lyHash.Hex}, reader); err != nil {
			return nil, err
		}

		size = size + int(manifest.Layers[idx].Size)
	}

	if re := mh.Put(ctx, repo, tag, mhash.String(), &model.RepoSimpleManifest{
		ContentType: string(manifest.MediaType),
		Blob:        bs,
	}); re != nil {
		return nil, err
	}

	return manifest, nil
}