package native

import (
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/config"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/dao"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/interop"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

// Treasury represents Treasury native contract.
type Treasury struct {
	interop.ContractMD
	Tutus ITutus
}

// Storage key prefixes for Treasury.
const (
	prefixTreasuryDeficit byte = 0x01 // -> *big.Int (cumulative deficit from fee subsidies)
)

var _ interop.Contract = (*Treasury)(nil)

func newTreasury() *Treasury {
	t := &Treasury{
		ContractMD: *interop.NewContractMD(nativenames.Treasury, nativeids.Treasury, func(m *manifest.Manifest, hardfork config.Hardfork) {
			m.SupportedStandards = []string{manifest.NEP26StandardName, manifest.NEP27StandardName, manifest.NEP30StandardName}
		}),
	}
	defer t.BuildHFSpecificMD(t.ActiveIn())

	desc := NewDescriptor("verify", smartcontract.BoolType)
	md := NewMethodAndPrice(t.verify, 1<<5, callflag.ReadStates)
	t.AddMethod(md, desc)

	desc = NewDescriptor("onNEP11Payment", smartcontract.VoidType,
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("tokenId", smartcontract.ByteArrayType),
		manifest.NewParameter("data", smartcontract.AnyType))
	md = NewMethodAndPrice(t.onNEP11Payment, 1<<5, callflag.NoneFlag)
	t.AddMethod(md, desc)

	desc = NewDescriptor("onNEP17Payment", smartcontract.VoidType,
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("data", smartcontract.AnyType))
	md = NewMethodAndPrice(t.onNEP17Payment, 1<<5, callflag.NoneFlag)
	t.AddMethod(md, desc)

	return t
}

// OnPersist implements the [interop.Contract] interface.
func (t *Treasury) OnPersist(ic *interop.Context) error {
	return nil
}

// PostPersist implements the [interop.Contract] interface.
func (t *Treasury) PostPersist(ic *interop.Context) error {
	return nil
}

// Metadata implements the [interop.Contract] interface.
func (t *Treasury) Metadata() *interop.ContractMD {
	return &t.ContractMD
}

// Initialize implements the [interop.Contract] interface.
func (t *Treasury) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	return nil
}

// InitializeCache implements the [interop.Contract] interface.
func (t *Treasury) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	return nil
}

// ActiveIn implements the [interop.Contract] interface.
func (t *Treasury) ActiveIn() *config.Hardfork {
	var f = config.HFFaun
	return &f
}

func (t *Treasury) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBool(t.Tutus.CheckCommittee(ic))
}

func (t *Treasury) onNEP11Payment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	// Arguments must be accessed and parsed to match the reference behaviour, but no real action is performed.
	from := toUint160(args[0])
	amount := toBigInt(args[1])
	tokenId := toBytes(args[2])
	data := args[3]

	var _, _, _, _ = from, amount, tokenId, data
	return stackitem.Null{}
}

func (t *Treasury) onNEP17Payment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	// Arguments must be accessed and parsed to match the reference behaviour, but no real action is performed.
	from := toUint160(args[0])
	amount := toBigInt(args[1])
	data := args[2]

	var _, _, _ = from, amount, data
	return stackitem.Null{}
}

// Public methods for cross-native access

// Address returns the contract's script hash.
func (t *Treasury) Address() util.Uint160 {
	return t.Hash
}

// Storage key helpers

func makeDeficitKey() []byte {
	return []byte{prefixTreasuryDeficit}
}

// AddDeficit adds to the cumulative deficit (for cross-native access).
// This is called when Treasury cannot cover fee subsidies for Vita holders.
func (t *Treasury) AddDeficit(d *dao.Simple, amount *big.Int) {
	if amount.Sign() <= 0 {
		return
	}
	current := t.GetDeficit(d)
	newDeficit := new(big.Int).Add(current, amount)
	d.PutStorageItem(t.ID, makeDeficitKey(), newDeficit.Bytes())
}

// GetDeficit returns the current cumulative deficit (for cross-native access).
func (t *Treasury) GetDeficit(d *dao.Simple) *big.Int {
	si := d.GetStorageItem(t.ID, makeDeficitKey())
	if si == nil {
		return big.NewInt(0)
	}
	return new(big.Int).SetBytes(si)
}
