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

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

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/crane"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/remote/transport"

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

const (
	indexMediaType    = "application/vnd.oci.image.index.v1+json"
	manifestMediaType = "application/vnd.oci.image.manifest.v1+json"
)

func (cli *Client) ImageManifest(ctx context.Context, imagepath string) (any, error) {
	var err error
	var res any

	imagepath, username, password, err := repackReference(imagepath)
	if err != nil {
		return res, err
	}
	options := make([]crane.Option, 0)
	options = append(options, crane.WithContext(ctx))

	ref, err := name.ParseReference(imagepath)
	if err != nil {
		return res, err
	}
	repo := ref.Context()
	if err != nil {
		return res, err
	}

	// Manifest
	if username != "" && password != "" {
		defaultTransport := NewRoundTripper(cli.skipTLSVerify)
		scopes := []string{repo.Scope(transport.PullScope)}

		regName := repo.RegistryStr()
		reg, err := name.NewRegistry(regName)
		if err != nil {
			return res, err
		}
		basicAuth := &authn.Basic{
			Username: username,
			Password: password,
		}
		authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes)
		if err != nil {
			return res, err
		}
		options = append(options, crane.WithTransport(authTransport))
	} else {
		transport := NewRoundTripper(cli.skipTLSVerify)
		options = append(options, crane.WithTransport(transport))
	}

	manifestBytes, err := crane.Manifest(imagepath, options...)
	if err != nil {
		return res, err
	}

	var descr ocispec.Descriptor
	err = json.Unmarshal(manifestBytes, &descr)
	if err != nil {
		return res, err
	}
	switch descr.MediaType {
	case indexMediaType:
		var index ocispec.Index
		err = json.Unmarshal(manifestBytes, &index)
		if err != nil {
			return res, err
		}
		res = index
	//case manifestMediaType:
	default:
		var manifest ocispec.Manifest
		err = json.Unmarshal(manifestBytes, &manifest)
		if err != nil {
			return res, err
		}
		res = &manifest
		fmt.Printf("Unknown or empty media type: %s", descr.MediaType)
	}
	return res, err
}

func (cli *Client) ImageTags(ctx context.Context, imagepath string) ([]string, error) {
	var err error
	res := make([]string, 0)

	imagepath, username, password, err := repackReference(imagepath)
	if err != nil {
		return res, err
	}
	options := make([]crane.Option, 0)
	options = append(options, crane.WithContext(ctx))

	ref, err := name.ParseReference(imagepath)
	if err != nil {
		return res, err
	}
	repo := ref.Context()
	if err != nil {
		return res, err
	}

	// Tags
	remoteOptions := make([]remote.Option, 0)
	remoteOptions = append(remoteOptions, remote.WithContext(ctx))

	if username != "" && password != "" {
		defaultTransport := NewRoundTripper(cli.skipTLSVerify)
		scopes := []string{repo.Scope(transport.PullScope)}

		regName := repo.RegistryStr()
		reg, err := name.NewRegistry(regName)
		if err != nil {
			return res, err
		}
		basicAuth := &authn.Basic{
			Username: username,
			Password: password,
		}
		authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes)
		if err != nil {
			return res, err
		}
		remoteOptions = append(remoteOptions, remote.WithTransport(authTransport))
	} else {
		transport := NewRoundTripper(cli.skipTLSVerify)
		options = append(options, crane.WithTransport(transport))
	}

	tags, err := remote.List(repo, remoteOptions...)
	if err != nil {
		return res, err
	}
	res = tags
	return res, err
}

func (cli *Client) ImageConfig(ctx context.Context, imagepath string) (*ocispec.Image, error) {
	var err error
	res := &ocispec.Image{}

	imagepath, username, password, err := repackReference(imagepath)
	if err != nil {
		return res, err
	}
	options := make([]crane.Option, 0)
	options = append(options, crane.WithContext(ctx))

	ref, err := name.ParseReference(imagepath)
	if err != nil {
		return res, err
	}
	repo := ref.Context()
	if err != nil {
		return res, err
	}

	// Config
	if username != "" && password != "" {
		defaultTransport := NewRoundTripper(cli.skipTLSVerify)
		scopes := []string{repo.Scope(transport.PullScope)}

		regName := repo.RegistryStr()
		reg, err := name.NewRegistry(regName)
		if err != nil {
			return res, err
		}
		basicAuth := &authn.Basic{
			Username: username,
			Password: password,
		}
		authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes)
		if err != nil {
			return res, err
		}
		options = append(options, crane.WithTransport(authTransport))
	} else {
		transport := NewRoundTripper(cli.skipTLSVerify)
		options = append(options, crane.WithTransport(transport))
	}
	configBytes, err := crane.Config(imagepath, options...)
	if err != nil {
		return res, err
	}
	var config ocispec.Image
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		return res, err
	}
	res = &config
	return res, err
}

func (cli *Client) CatalogImages(ctx context.Context, source string) ([]string, error) {
	var err error
	res := make([]string, 0)

	source, _, _, err = repackSource(source)
	if err != nil {
		return res, err
	}
	options := make([]crane.Option, 0)
	options = append(options, crane.WithContext(ctx))

	// Config
	transport := NewRoundTripper(cli.skipTLSVerify)
	options = append(options, crane.WithTransport(transport))
	res, err = crane.Catalog(source, options...)
	if err != nil {
		return res, err
	}
	return res, err
}
