// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package wire

import (
	"bytes"
	"fmt"

	coreerr "dappco.re/go/core/log"

	"dappco.re/go/core/blockchain/types"
)

// VariantElement is one tagged element from a raw variant vector.
// Data contains the raw wire bytes for the element payload, without the tag.
type VariantElement struct {
	Tag  uint8
	Data []byte
}

// DecodeVariantVector decodes a raw variant vector into tagged raw elements.
// It is useful for higher-level validation of raw transaction fields such as
// extra, attachment, signatures, and proofs.
func DecodeVariantVector(raw []byte) ([]VariantElement, error) {
	dec := NewDecoder(bytes.NewReader(raw))
	count := dec.ReadVarint()
	if dec.Err() != nil {
		return nil, dec.Err()
	}

	elems := make([]VariantElement, 0, int(count))
	for i := uint64(0); i < count; i++ {
		tag := dec.ReadUint8()
		if dec.Err() != nil {
			return nil, coreerr.E("DecodeVariantVector", fmt.Sprintf("read tag %d", i), dec.Err())
		}

		data := readVariantElementData(dec, tag)
		if dec.Err() != nil {
			return nil, coreerr.E("DecodeVariantVector", fmt.Sprintf("read element %d", i), dec.Err())
		}

		elems = append(elems, VariantElement{Tag: tag, Data: data})
	}

	return elems, nil
}

// DecodeAssetDescriptorOperation decodes a raw asset_descriptor_operation
// payload into its typed representation.
func DecodeAssetDescriptorOperation(raw []byte) (types.AssetDescriptorOperation, error) {
	dec := NewDecoder(bytes.NewReader(raw))
	_, op := parseAssetDescriptorOperation(dec)

	if dec.Err() != nil {
		return types.AssetDescriptorOperation{}, coreerr.E("DecodeAssetDescriptorOperation", "decode asset descriptor operation", dec.Err())
	}

	return op, nil
}

// parseAssetDescriptorOperation is the single source of truth for both raw
// wire preservation and typed HF5 asset operation decoding.
func parseAssetDescriptorOperation(dec *Decoder) ([]byte, types.AssetDescriptorOperation) {
	var raw []byte
	var op types.AssetDescriptorOperation

	appendByte := func(v uint8) {
		raw = append(raw, v)
	}
	appendBytes := func(v []byte) {
		raw = append(raw, v...)
	}

	op.Version = dec.ReadUint8()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendByte(op.Version)

	op.OperationType = dec.ReadUint8()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendByte(op.OperationType)

	assetMarker := dec.ReadUint8()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendByte(assetMarker)
	if assetMarker != 0 {
		assetID := dec.ReadBytes(32)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		copy(op.AssetID[:], assetID)
		appendBytes(assetID)
	}

	descMarker := dec.ReadUint8()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendByte(descMarker)
	if descMarker != 0 {
		desc := &types.AssetDescriptorBase{}

		tickerRaw := readStringBlob(dec)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		desc.Ticker = decodeStringBlob(tickerRaw)
		appendBytes(tickerRaw)

		fullNameRaw := readStringBlob(dec)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		desc.FullName = decodeStringBlob(fullNameRaw)
		appendBytes(fullNameRaw)

		desc.TotalMaxSupply = dec.ReadUint64LE()
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		appendBytes(uint64LEBytes(desc.TotalMaxSupply))

		desc.CurrentSupply = dec.ReadUint64LE()
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		appendBytes(uint64LEBytes(desc.CurrentSupply))

		desc.DecimalPoint = dec.ReadUint8()
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		appendByte(desc.DecimalPoint)

		metaInfoRaw := readStringBlob(dec)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		desc.MetaInfo = decodeStringBlob(metaInfoRaw)
		appendBytes(metaInfoRaw)

		ownerKey := dec.ReadBytes(32)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		copy(desc.OwnerKey[:], ownerKey)
		appendBytes(ownerKey)

		desc.Etc = readVariantVectorFixed(dec, 1)
		if dec.Err() != nil {
			return nil, types.AssetDescriptorOperation{}
		}
		appendBytes(desc.Etc)

		op.Descriptor = desc
	}

	op.AmountToEmit = dec.ReadUint64LE()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendBytes(uint64LEBytes(op.AmountToEmit))

	op.AmountToBurn = dec.ReadUint64LE()
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendBytes(uint64LEBytes(op.AmountToBurn))

	op.Etc = readVariantVectorFixed(dec, 1)
	if dec.Err() != nil {
		return nil, types.AssetDescriptorOperation{}
	}
	appendBytes(op.Etc)

	return raw, op
}

func decodeStringBlob(raw []byte) string {
	return string(raw[varintPrefixLen(raw):])
}

func varintPrefixLen(raw []byte) int {
	n := 0
	for n < len(raw) {
		n++
		if raw[n-1] < 0x80 {
			return n
		}
	}
	return len(raw)
}

func uint64LEBytes(v uint64) []byte {
	return []byte{
		byte(v),
		byte(v >> 8),
		byte(v >> 16),
		byte(v >> 24),
		byte(v >> 32),
		byte(v >> 40),
		byte(v >> 48),
		byte(v >> 56),
	}
}
