/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 *
 * This work is published and licensed under a Creative Commons
 * Attribution-NonCommercial-NoDerivatives 4.0 International License.
 *
 * Distribution of this work is permitted, but commercial use and
 * modifications are strictly prohibited.
 */
package imageoper

import (
	"encoding/json"

	"mstore/pkg/auxoci"
	"mstore/pkg/auxtool"
	"mstore/pkg/auxuuid"
	"mstore/pkg/descr"

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

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

func indexFromManigestDescrs(manifestDescrs []descr.Manifest) (ocispec.Index, string, error) {
	var err error
	var payload string
	index := ocispec.Index{
		MediaType: oiiMediaType,
		Manifests: make([]ocispec.Descriptor, 0),
	}
	index.Versioned.SchemaVersion = 2
	for _, manifestDescr := range manifestDescrs {
		var ociManifest ocispec.Manifest

		ociManifest.Subject = &ocispec.Descriptor{}
		ociManifest.Subject.Platform = &ocispec.Platform{}
		ociManifest.Config = ocispec.Descriptor{}
		ociManifest.Config.Platform = &ocispec.Platform{}

		err = json.Unmarshal([]byte(manifestDescr.Payload), &ociManifest)
		if err != nil {
			return index, payload, err
		}
		descriptor := ocispec.Descriptor{
			MediaType: oimMediaType,
			Digest:    auxoci.SHA256DigestFromString(manifestDescr.Payload),
			Size:      int64(len(manifestDescr.Payload)),
			Platform:  ociManifest.Subject.Platform,
		}
		index.Manifests = append(index.Manifests, descriptor)
	}
	indexBytes, err := json.Marshal(index)
	if err != nil {
		return index, payload, err
	}
	payload = string(indexBytes)
	return index, payload, err
}

func descrsFromManifest(name, reference string, manifest *ocispec.Manifest, rawManifest []byte) (descr.Manifest, []descr.Blob, error) {
	var err error
	manifestDescr := descr.Manifest{}
	//configDescr := descr.Blob{}
	blobDescrs := make([]descr.Blob, 0)

	timestamp := auxtool.TimeNow()

	// Make manifest descriptor
	manifestDigest := ocidigest.SHA256.FromBytes(rawManifest).String()
	manifestDescr = descr.Manifest{
		ID:          auxuuid.NewUUID(),
		Name:        name,
		Reference:   reference,
		Digest:      manifestDigest,
		ContentType: manifest.MediaType,
		Payload:     string(rawManifest),
		CreatedAt:   timestamp,
		UpdatedAt:   timestamp,
	}
	// Make config descriptor
	ociConfig := manifest.Config
	configDescr := descr.Blob{
		ID:        auxuuid.NewUUID(),
		Name:      name,
		Reference: reference,
		MediaType: ociConfig.MediaType,
		Digest:    string(ociConfig.Digest),
		Size:      ociConfig.Size,
		CreatedAt: timestamp,
		UpdatedAt: timestamp,
	}
	blobDescrs = append(blobDescrs, configDescr)
	// Make blob descriptions
	layerMap := make(map[string]bool)
	for _, layer := range manifest.Layers {
		blobDescr := descr.Blob{
			ID:        auxuuid.NewUUID(),
			Name:      name,
			Reference: reference,
			MediaType: layer.MediaType,
			Digest:    string(layer.Digest),
			Size:      layer.Size,
			CreatedAt: timestamp,
			UpdatedAt: timestamp,
		}
		_, alreadyAdded := layerMap[string(layer.Digest)]
		if !alreadyAdded {
			blobDescrs = append(blobDescrs, blobDescr)
			layerMap[string(layer.Digest)] = true
		}
	}
	return manifestDescr, blobDescrs, err
}

func layersDiff(name, reference string, existingManifest, incomingManifest *ocispec.Manifest, rawManifest []byte) ([]descr.Blob, []descr.Blob, error) {
	var err error
	newBlobDescrs := make([]descr.Blob, 0)
	delBlobDescrs := make([]descr.Blob, 0)

	// Calculating old layers
	delLayers := make([]ocispec.Descriptor, 0)
	for _, oldLayer := range existingManifest.Layers {
		uniq := true
		for _, newLayer := range incomingManifest.Layers {
			if oldLayer.Digest == newLayer.Digest {
				uniq = false
			}
		}
		if uniq {
			delLayers = append(delLayers, oldLayer)
		}
	}
	// Calculating new layers
	newLayers := make([]ocispec.Descriptor, 0)
	for _, newLayer := range incomingManifest.Layers {
		uniq := true
		for _, oldLayer := range existingManifest.Layers {
			if oldLayer.Digest == newLayer.Digest {
				uniq = false
			}
		}
		if uniq {
			newLayers = append(newLayers, newLayer)
		}
	}
	if incomingManifest.Config.Digest != existingManifest.Config.Digest {
		delLayers = append(delLayers, existingManifest.Config)
		newLayers = append(newLayers, incomingManifest.Config)
	}
	// Converting to new blob
	timestamp := auxtool.TimeNow()
	for _, layer := range newLayers {
		blobDescr := descr.Blob{
			ID:        auxuuid.NewUUID(),
			Name:      name,
			Reference: reference,
			MediaType: layer.MediaType,
			Digest:    string(layer.Digest),
			Size:      layer.Size,
			CreatedAt: timestamp,
			UpdatedAt: timestamp,
		}
		newBlobDescrs = append(newBlobDescrs, blobDescr)
	}
	// Converting to old blobs
	for _, layer := range delLayers {
		blobDescr := descr.Blob{
			ID:        auxuuid.NewUUID(),
			Name:      name,
			Reference: reference,
			MediaType: layer.MediaType,
			Digest:    string(layer.Digest),
			Size:      layer.Size,
		}
		delBlobDescrs = append(delBlobDescrs, blobDescr)
	}
	return newBlobDescrs, delBlobDescrs, err
}
