package blobovnicza

import (
	"encoding/binary"
	"errors"
	"fmt"
	"strings"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"github.com/mr-tron/base58"
)

var BlobovniczaParser = common.WithFallback(
	common.Any(
		MetaBucketParser,
		BucketParser,
	),
	common.RawParser.ToFallbackParser(),
)

func MetaBucketParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
	if value != nil {
		return nil, nil, errors.New("not a bucket")
	}

	if string(key) != "META" {
		return nil, nil, errors.New("invalid bucket name")
	}

	return &MetaBucket{}, MetaRecordParser, nil
}

func MetaRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
	var r MetaRecord

	if len(key) == 0 {
		return nil, nil, errors.New("invalid key")
	}

	r.label = string(key)
	r.count = binary.LittleEndian.Uint64(value)

	return &r, nil, nil
}

func BucketParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
	if value != nil {
		return nil, nil, errors.New("not a bucket")
	}

	size, n := binary.Varint(key)
	if n <= 0 {
		return nil, nil, errors.New("invalid size")
	}

	return &Bucket{size: size}, RecordParser, nil
}

func RecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
	parts := strings.Split(string(key), "/")

	if len(parts) != 2 {
		return nil, nil, errors.New("invalid key, expected address string <CID>/<OID>")
	}

	cnrRaw, err := base58.Decode(parts[0])
	if err != nil {
		return nil, nil, errors.New("can't decode CID string")
	}
	objRaw, err := base58.Decode(parts[1])
	if err != nil {
		return nil, nil, errors.New("can't decode OID string")
	}

	cnr := cid.ID{}
	if err := cnr.Decode(cnrRaw); err != nil {
		return nil, nil, fmt.Errorf("can't decode CID: %w", err)
	}
	obj := oid.ID{}
	if err := obj.Decode(objRaw); err != nil {
		return nil, nil, fmt.Errorf("can't decode OID: %w", err)
	}

	var r Record

	r.addr.SetContainer(cnr)
	r.addr.SetObject(obj)

	if err := r.object.Unmarshal(value); err != nil {
		return nil, nil, errors.New("can't unmarshal object")
	}

	return &r, nil, nil
}