// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package genesis

import (
	"cmp"
	"errors"
	"fmt"

	"github.com/luxfi/address"
	"github.com/luxfi/ids"
	lux "github.com/luxfi/utxo"
	"github.com/luxfi/node/vms/platformvm/stakeable"
	"github.com/luxfi/node/vms/platformvm/txs"
	"github.com/luxfi/node/vms/platformvm/txs/txheap"
	"github.com/luxfi/utils"
	"github.com/luxfi/math"
	"github.com/luxfi/node/vms/platformvm/signer"
	"github.com/luxfi/utxo/secp256k1fx"
)

// Note that since a Lux network has exactly one Platform Chain,
// and the Platform Chain defines the genesis state of the network
// (who is staking, which chains exist, etc.), defining the genesis
// state of the Platform Chain is the same as defining the genesis
// state of the network.

var (
	errUTXOHasNoValue         = errors.New("genesis UTXO has no value")
	errValidatorHasZeroWeight   = errors.New("validator has zero weight")
	errValidatorAlreadyExited = errors.New("validator would have already unstaked")
	errStakeOverflow          = errors.New("validator stake exceeds limit")

	_ utils.Sortable[Allocation] = Allocation{}
)

// UTXO adds messages to UTXOs
type UTXO struct {
	lux.UTXO `serialize:"true"`
	Message  []byte `serialize:"true" json:"message"`
}

// Genesis represents a genesis state of the platform chain
type Genesis struct {
	UTXOs         []*UTXO   `serialize:"true"`
	Validators    []*txs.Tx `serialize:"true"`
	Chains        []*txs.Tx `serialize:"true"`
	Timestamp     uint64    `serialize:"true"`
	InitialSupply uint64    `serialize:"true"`
	Message       string    `serialize:"true"`
}

// Parse deserializes a P-Chain genesis blob produced by either the
// v0 (v1.23.x) or v1 (current) codec. Wire version is taken from the
// 2-byte codec prefix; the matching codec.Manager entry is used to
// unmarshal the outer Genesis struct, and embedded validator + chain
// txs are bound to their original signedBytes via InitializeFromBytes
// (NOT Initialize, which would re-marshal and rotate the TxID).
//
// TxID stability across the v0->v1 migration is preserved because:
//   - v0 genesis blobs decode via the v0 slot map and embedded txs are
//     re-bound at version 0; their hash(signedBytes) is identical to
//     what the v1.23.x producer committed.
//   - v1 genesis blobs decode via the v1 slot map; embedded txs are
//     re-bound at version 1; bytes are deterministic so the result is
//     byte-equal to what a v1 producer emits.
//
// Genesis blobs are typically produced fresh from the JSON config at
// each bootstrap (see New + Bytes), so the path that matters for
// mainnet/testnet is the v0 fallback: the originally-committed genesis
// hash is what subsequent block headers chain back to, and that hash
// is hash(v0 genesis bytes).
func Parse(genesisBytes []byte) (*Genesis, error) {
	gen := &Genesis{}
	version, err := Codec.Unmarshal(genesisBytes, gen)
	if err != nil {
		return nil, err
	}
	for _, tx := range gen.Validators {
		if err := tx.InitializeFromBytesAtVersion(txs.GenesisCodec, version); err != nil {
			return nil, err
		}
	}
	for _, tx := range gen.Chains {
		if err := tx.InitializeFromBytesAtVersion(txs.GenesisCodec, version); err != nil {
			return nil, err
		}
	}
	return gen, nil
}

// Allocation is a UTXO on the Platform Chain that exists at the chain's genesis
type Allocation struct {
	Locktime uint64
	Amount   uint64
	Address  string
	Message  []byte
}

// Compare compares two allocations
func (a Allocation) Compare(other Allocation) int {
	if locktimeCmp := cmp.Compare(a.Locktime, other.Locktime); locktimeCmp != 0 {
		return locktimeCmp
	}
	if amountCmp := cmp.Compare(a.Amount, other.Amount); amountCmp != 0 {
		return amountCmp
	}

	addr, err := bech32ToID(a.Address)
	if err != nil {
		return 0
	}

	otherAddr, err := bech32ToID(other.Address)
	if err != nil {
		return 0
	}

	return addr.Compare(otherAddr)
}

// Validator represents a validator at genesis
type Validator struct {
	TxID      ids.ID
	StartTime uint64
	EndTime   uint64
	Weight    uint64
	NodeID    ids.NodeID
}

// Owner is the repr. of a reward owner at genesis
type Owner struct {
	Locktime  uint64
	Threshold uint32
	Addresses []string
}

// GenesisPermissionlessValidator represents a permissionless validator at genesis
type PermissionlessValidator struct {
	Validator
	RewardOwner        *Owner
	DelegationFee      float32
	ExactDelegationFee uint32
	Staked             []Allocation
	Signer             *signer.ProofOfPossession
}

// Chain defines a chain that exists at the network's genesis
// [GenesisData] is the initial state of the chain.
// [VMID] is the ID of the VM this blockchain runs.
// [FxIDs] are the IDs of the Fxs the blockchain supports.
// [Name] is a human-readable, non-unique name for the blockchain.
// [ChainID] is the ID of the chain that validates the blockchain
type Chain struct {
	GenesisData []byte
	VMID        ids.ID
	FxIDs       []ids.ID
	Name        string
	ChainID     ids.ID
}

// bech32ToID takes bech32 address and produces a shortID
func bech32ToID(addrStr string) (ids.ShortID, error) {
	_, addrBytes, err := address.ParseBech32(addrStr)
	if err != nil {
		return ids.ShortID{}, err
	}
	return ids.ToShortID(addrBytes)
}

// New builds the genesis state of the P-Chain (and thereby the Lux network.)
// [utxoAssetID] is the ID of the LUX asset
// [networkID] is the ID of the network
// [allocations] are the UTXOs on the Platform Chain that exist at genesis.
// [validators] are the validators of the primary network at genesis.
// [chains] are the chains that exist at genesis.
// [time] is the Platform Chain's time at network genesis.
// [initialSupply] is the initial supply of the LUX asset.
// [message] is the message to be sent to the genesis UTXOs.
func New(
	utxoAssetID ids.ID,
	networkID uint32,
	allocations []Allocation,
	validators []PermissionlessValidator,
	chains []Chain,
	time uint64,
	initialSupply uint64,
	message string,
) (*Genesis, error) {
	// Specify the UTXOs on the Platform chain that exist at genesis
	utxos := make([]*UTXO, 0, len(allocations))
	for i, allocation := range allocations {
		if allocation.Amount == 0 {
			return nil, errUTXOHasNoValue
		}
		addrID, err := bech32ToID(allocation.Address)
		if err != nil {
			return nil, err
		}

		utxo := lux.UTXO{
			UTXOID: lux.UTXOID{
				TxID:        ids.Empty,
				OutputIndex: uint32(i),
			},
			Asset: lux.Asset{ID: utxoAssetID},
			Out: &secp256k1fx.TransferOutput{
				Amt: allocation.Amount,
				OutputOwners: secp256k1fx.OutputOwners{
					Locktime:  0,
					Threshold: 1,
					Addrs:     []ids.ShortID{addrID},
				},
			},
		}
		if allocation.Locktime > time {
			utxo.Out = &stakeable.LockOut{
				Locktime:        allocation.Locktime,
				TransferableOut: utxo.Out.(lux.TransferableOut),
			}
		}
		if err != nil {
			return nil, fmt.Errorf("problem decoding UTXO message bytes: %w", err)
		}
		utxos = append(utxos, &UTXO{
			UTXO:    utxo,
			Message: allocation.Message,
		})
	}

	// Specify the validators that are validating the primary network at genesis
	vdrs := txheap.NewByEndTime()
	for _, vdr := range validators {
		weight := uint64(0)
		stake := make([]*lux.TransferableOutput, len(vdr.Staked))
		utils.Sort(vdr.Staked)
		for i, allocation := range vdr.Staked {
			addrID, err := bech32ToID(allocation.Address)
			if err != nil {
				return nil, err
			}

			utxo := &lux.TransferableOutput{
				Asset: lux.Asset{ID: utxoAssetID},
				Out: &secp256k1fx.TransferOutput{
					Amt: allocation.Amount,
					OutputOwners: secp256k1fx.OutputOwners{
						Locktime:  0,
						Threshold: 1,
						Addrs:     []ids.ShortID{addrID},
					},
				},
			}
			if allocation.Locktime > time {
				utxo.Out = &stakeable.LockOut{
					Locktime:        allocation.Locktime,
					TransferableOut: utxo.Out,
				}
			}
			stake[i] = utxo

			newWeight, err := math.Add(weight, allocation.Amount)
			if err != nil {
				return nil, errStakeOverflow
			}
			weight = newWeight
		}

		// Use explicit weight from validator config if Staked allocations are empty
		// This allows validators to be created with explicit weights for testing/development
		if weight == 0 && vdr.Weight > 0 {
			weight = vdr.Weight
		}

		if weight == 0 {
			return nil, errValidatorHasZeroWeight
		}
		if vdr.EndTime <= time {
			return nil, errValidatorAlreadyExited
		}

		owner := &secp256k1fx.OutputOwners{
			Locktime:  vdr.RewardOwner.Locktime,
			Threshold: vdr.RewardOwner.Threshold,
		}
		for _, addrStr := range vdr.RewardOwner.Addresses {
			addrID, err := bech32ToID(addrStr)
			if err != nil {
				return nil, err
			}
			owner.Addrs = append(owner.Addrs, addrID)
		}
		utils.Sort(owner.Addrs)

		// When stakers have explicit weight but no staked allocations,
		// synthesize a stake output so that AddValidatorTx/AddPermissionlessValidatorTx
		// passes SyntacticVerify (which requires StakeOuts sum == Wght).
		if len(stake) == 0 && weight > 0 {
			stakeAddr := owner.Addrs[0]
			stake = []*lux.TransferableOutput{
				{
					Asset: lux.Asset{ID: utxoAssetID},
					Out: &secp256k1fx.TransferOutput{
						Amt: weight,
						OutputOwners: secp256k1fx.OutputOwners{
							Locktime:  0,
							Threshold: 1,
							Addrs:     []ids.ShortID{stakeAddr},
						},
					},
				},
			}
		}

		delegationFee := vdr.ExactDelegationFee

		var (
			baseTx = txs.BaseTx{BaseTx: lux.BaseTx{
				NetworkID:    networkID,
				BlockchainID: ids.Empty,
			}}
			validator = txs.Validator{
				NodeID: vdr.NodeID,
				Start:  time,
				End:    vdr.EndTime,
				Wght:   weight,
			}
			tx *txs.Tx
		)
		if vdr.Signer == nil {
			tx = &txs.Tx{Unsigned: &txs.AddValidatorTx{
				BaseTx:           baseTx,
				Validator:        validator,
				StakeOuts:        stake,
				RewardsOwner:     owner,
				DelegationShares: delegationFee,
			}}
		} else {
			tx = &txs.Tx{Unsigned: &txs.AddPermissionlessValidatorTx{
				BaseTx:                baseTx,
				Validator:             validator,
				Signer:                vdr.Signer,
				StakeOuts:             stake,
				ValidatorRewardsOwner: owner,
				DelegatorRewardsOwner: owner,
				DelegationShares:      delegationFee,
			}}
		}

		if err := tx.Initialize(txs.GenesisCodec); err != nil {
			return nil, err
		}

		vdrs.Add(tx)
	}

	// Specify the chains that exist at genesis
	chainsTxs := []*txs.Tx{}
	for _, chain := range chains {
		tx := &txs.Tx{Unsigned: &txs.CreateChainTx{
			BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{
				NetworkID:    networkID,
				BlockchainID: ids.Empty,
			}},
			ChainID:        chain.ChainID,
			BlockchainName: chain.Name,
			VMID:           chain.VMID,
			FxIDs:          chain.FxIDs,
			GenesisData:    chain.GenesisData,
			ChainAuth:      &secp256k1fx.Input{},
		}}
		if err := tx.Initialize(txs.GenesisCodec); err != nil {
			return nil, err
		}

		chainsTxs = append(chainsTxs, tx)
	}

	validatorTxs := vdrs.List()

	g := &Genesis{
		UTXOs:         utxos,
		Validators:    validatorTxs,
		Chains:        chainsTxs,
		Timestamp:     time,
		InitialSupply: initialSupply,
		Message:       message,
	}

	return g, nil
}

// Bytes serializes the Genesis to bytes using the PlatformVM genesis codec
func (g *Genesis) Bytes() ([]byte, error) {
	return Codec.Marshal(CodecVersion, g)
}
