package git_pages

import (
	"context"
	"fmt"

	"github.com/c2h5oh/datasize"
	"github.com/dghubble/trie"
)

func trieReduce(data trie.Trier) (items, total int64) {
	data.Walk(func(key string, value any) error {
		items += 1
		total += *value.(*int64)
		return nil
	})
	return
}

func TraceGarbage(ctx context.Context) error {
	allBlobs := trie.NewRuneTrie()
	liveBlobs := trie.NewRuneTrie()

	traceManifest := func(manifestName string, manifest *Manifest) error {
		for _, entry := range manifest.GetContents() {
			if entry.GetType() == Type_ExternalFile {
				blobName := string(entry.Data)
				if size := allBlobs.Get(blobName); size == nil {
					return fmt.Errorf("%s: dangling reference %s", manifestName, blobName)
				} else {
					liveBlobs.Put(blobName, size)
				}
			}
		}
		return nil
	}

	// Enumerate all blobs.
	for metadata, err := range backend.EnumerateBlobs(ctx) {
		if err != nil {
			return fmt.Errorf("trace blobs err: %w", err)
		}
		allBlobs.Put(metadata.Name, &metadata.Size)
	}

	// Enumerate blobs live via site manifests.
	for metadata, err := range backend.EnumerateManifests(ctx) {
		if err != nil {
			return fmt.Errorf("trace sites err: %w", err)
		}
		manifest, _, err := backend.GetManifest(ctx, metadata.Name, GetManifestOptions{})
		if err != nil {
			return fmt.Errorf("trace sites err: %w", err)
		}
		err = traceManifest(metadata.Name, manifest)
		if err != nil {
			return fmt.Errorf("trace sites err: %w", err)
		}
	}

	// Enumerate blobs live via audit records.
	for auditID, err := range backend.SearchAuditLog(ctx, SearchAuditLogOptions{}) {
		if err != nil {
			return fmt.Errorf("trace audit err: %w", err)
		}
		auditRecord, err := backend.QueryAuditLog(ctx, auditID)
		if err != nil {
			return fmt.Errorf("trace audit err: %w", err)
		}
		if auditRecord.Manifest != nil {
			err = traceManifest(auditID.String(), auditRecord.Manifest)
			if err != nil {
				return fmt.Errorf("trace audit err: %w", err)
			}
		}
	}

	allBlobsCount, allBlobsSize := trieReduce(allBlobs)
	liveBlobsCount, liveBlobsSize := trieReduce(liveBlobs)
	logc.Printf(ctx, "trace all: %d blobs, %s",
		allBlobsCount, datasize.ByteSize(allBlobsSize).HR())
	logc.Printf(ctx, "trace live: %d blobs, %s",
		liveBlobsCount, datasize.ByteSize(liveBlobsSize).HR())
	logc.Printf(ctx, "trace dead: %d blobs, %s",
		allBlobsCount-liveBlobsCount, datasize.ByteSize(allBlobsSize-liveBlobsSize).HR())

	return nil
}
