package nep17

import (
	"git.marketally.com/tutus-one/tutus-chain/pkg/interop"
	"git.marketally.com/tutus-one/tutus-chain/pkg/interop/contract"
	"git.marketally.com/tutus-one/tutus-chain/pkg/interop/native/management"
	"git.marketally.com/tutus-one/tutus-chain/pkg/interop/runtime"
	"git.marketally.com/tutus-one/tutus-chain/pkg/interop/storage"
)

// Token holds all token info.
type Token struct {
	// Token name
	Name string
	// Ticker symbol
	Symbol string
	// Amount of decimals
	Decimals int
	// Token owner address
	Owner []byte
	// Total tokens * multiplier
	TotalSupply int
	// Storage key for circulation value
	CirculationKey string
}

// getIntFromDB is a helper that checks for nil result of storage.Get and returns
// zero as the default value.
func getIntFromDB(ctx storage.Context, key []byte) int {
	var res int
	val := storage.Get(ctx, key)
	if val != nil {
		res = val.(int)
	}
	return res
}

// GetSupply gets the token totalSupply value from VM storage.
func (t Token) GetSupply(ctx storage.Context) int {
	return getIntFromDB(ctx, []byte(t.CirculationKey))
}

// BalanceOf gets the token balance of a specific address.
func (t Token) BalanceOf(ctx storage.Context, holder []byte) int {
	return getIntFromDB(ctx, holder)
}

// Transfer token from one user to another.
func (t Token) Transfer(ctx storage.Context, from, to interop.Hash160, amount int, data any) bool {
	if amount < 0 {
		panic("negative amount")
	}

	amountFrom := t.CanTransfer(ctx, from, to, amount)
	if amountFrom == -1 {
		return false
	}

	if amountFrom == 0 {
		storage.Delete(ctx, from)
	}

	if amountFrom > 0 {
		diff := amountFrom - amount
		storage.Put(ctx, from, diff)
	}

	amountTo := getIntFromDB(ctx, to)
	totalAmountTo := amountTo + amount
	if totalAmountTo != 0 {
		storage.Put(ctx, to, totalAmountTo)
	}

	runtime.Notify("Transfer", from, to, amount)
	if to != nil && management.GetContract(to) != nil {
		contract.Call(to, "onNEP17Payment", contract.All, from, amount, data)
	}
	return true
}

// CanTransfer returns the amount it can transfer.
func (t Token) CanTransfer(ctx storage.Context, from []byte, to []byte, amount int) int {
	if len(to) != 20 || !IsUsableAddress(from) {
		return -1
	}

	amountFrom := getIntFromDB(ctx, from)
	if amountFrom < amount {
		return -1
	}

	// Tell Transfer the result is equal - special case since it uses Delete
	if amountFrom == amount {
		return 0
	}

	// return amountFrom value back to Transfer, reduces extra Get
	return amountFrom
}

// IsUsableAddress checks if the sender is either the correct Neo address or SC address.
func IsUsableAddress(addr []byte) bool {
	if len(addr) == 20 {
		if runtime.CheckWitness(addr) {
			return true
		}

		// Check if a smart contract is calling scripthash
		callingScriptHash := runtime.GetCallingScriptHash()
		if callingScriptHash.Equals(addr) {
			return true
		}
	}

	return false
}

// Mint initial supply of tokens.
func (t Token) Mint(ctx storage.Context, to interop.Hash160) bool {
	if !IsUsableAddress(t.Owner) {
		return false
	}
	minted := storage.Get(ctx, []byte("minted"))
	if minted != nil && minted.(bool) {
		return false
	}

	storage.Put(ctx, to, t.TotalSupply)
	storage.Put(ctx, []byte("minted"), true)
	storage.Put(ctx, []byte(t.CirculationKey), t.TotalSupply)
	var from interop.Hash160
	runtime.Notify("Transfer", from, to, t.TotalSupply)
	return true
}
