package native

import (
	"errors"
	"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/core/state"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/transaction"
	"git.marketally.com/tutus-one/tutus-chain/pkg/crypto/hash"
	"git.marketally.com/tutus-one/tutus-chain/pkg/crypto/keys"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
)

// Lub represents Lub native contract (utility/fee token - lubrication).
type Lub struct {
	nep17TokenNative
	Tutus ITutus
	Policy     IPolicy
	Vita       IVita       // For checking local Vita status
	Federation IFederation // For visiting Vita and fee split
	Treasury   ITreasury   // For subsidizing fees

	initialSupply int64
}

// VitaExemptType indicates the type of Vita exemption for fee purposes.
type VitaExemptType int

const (
	// VitaExemptNone indicates no Vita exemption.
	VitaExemptNone VitaExemptType = iota
	// VitaExemptLocal indicates a local Vita holder (100% paid by local Treasury).
	VitaExemptLocal
	// VitaExemptVisitor indicates a visiting Vita holder (split between local and home chain).
	VitaExemptVisitor
	// VitaExemptAsylum indicates an asylum seeker (100% local Treasury, no inter-chain debt).
	// Used for humanitarian override when home chain becomes hostile.
	VitaExemptAsylum
)

// LubFactor is a divisor for finding Lub integral value.
const LubFactor = TutusTotalSupply

// newLub returns Lub native contract.
func newLub(init int64) *Lub {
	g := &Lub{
		initialSupply: init,
	}
	defer g.BuildHFSpecificMD(g.ActiveIn())

	nep17 := newNEP17Native(nativenames.Lub, nativeids.Lub, nil)
	nep17.symbol = "LUB"
	nep17.decimals = 8
	nep17.factor = LubFactor
	nep17.incBalance = g.increaseBalance
	nep17.balFromBytes = g.balanceFromBytes

	g.nep17TokenNative = *nep17

	return g
}

func (g *Lub) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) (func(), error) {
	acc, err := state.NEP17BalanceFromBytes(*si)
	if err != nil {
		return nil, err
	}
	if sign := amount.Sign(); sign == 0 {
		// Requested self-transfer amount can be higher than actual balance.
		if checkBal != nil && acc.Balance.Cmp(checkBal) < 0 {
			err = errors.New("insufficient funds")
		}
		return nil, err
	} else if sign == -1 && acc.Balance.CmpAbs(amount) == -1 {
		return nil, errors.New("insufficient funds")
	}
	acc.Balance.Add(&acc.Balance, amount)
	if acc.Balance.Sign() != 0 {
		*si = acc.Bytes(nil)
	} else {
		*si = nil
	}
	return nil, nil
}

func (g *Lub) balanceFromBytes(si *state.StorageItem) (*big.Int, error) {
	acc, err := state.NEP17BalanceFromBytes(*si)
	if err != nil {
		return nil, err
	}
	return &acc.Balance, err
}

// Initialize initializes a Lub contract.
func (g *Lub) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	if hf != g.ActiveIn() {
		return nil
	}

	if err := g.nep17TokenNative.Initialize(ic); err != nil {
		return err
	}
	_, totalSupply := g.getTotalSupply(ic.DAO)
	if totalSupply.Sign() != 0 {
		return errors.New("already initialized")
	}
	h, err := getStandbyValidatorsHash(ic)
	if err != nil {
		return err
	}
	g.Mint(ic, h, big.NewInt(g.initialSupply), false)
	return nil
}

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

// OnPersist implements the Contract interface.
func (g *Lub) OnPersist(ic *interop.Context) error {
	if len(ic.Block.Transactions) == 0 {
		return nil
	}
	for _, tx := range ic.Block.Transactions {
		absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
		sender := tx.Sender()

		// Check fee exemption type
		exemptType := g.getVitaExemptType(ic.DAO, sender)

		switch exemptType {
		case VitaExemptLocal:
			// Local citizen: 100% from Treasury
			g.burnFromTreasury(ic, absAmount)
		case VitaExemptVisitor:
			// Visitor: split between local Treasury and inter-chain debt
			var homeChain uint32
			if g.Federation != nil {
				homeChain = g.Federation.GetHomeChain(ic.DAO, sender)
			}
			g.burnFromTreasuryWithSplit(ic, absAmount, homeChain)
		case VitaExemptAsylum:
			// Asylum seeker: 100% from local Treasury, no inter-chain debt
			// Humanitarian override - don't fund hostile home chain
			g.burnFromTreasury(ic, absAmount)
		default:
			// Non-citizen: burn from sender
			g.Burn(ic, sender, absAmount)
		}
	}
	validators := g.Tutus.GetNextBlockValidatorsInternal(ic.DAO)
	primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
	var netFee int64
	for _, tx := range ic.Block.Transactions {
		netFee += tx.NetworkFee
		// Reward for NotaryAssisted attribute will be minted to designated notary nodes
		// by Notary contract.
		attrs := tx.GetAttributes(transaction.NotaryAssistedT)
		if len(attrs) != 0 {
			na := attrs[0].Value.(*transaction.NotaryAssisted)
			netFee -= (int64(na.NKeys) + 1) * g.Policy.GetAttributeFeeInternal(ic.DAO, transaction.NotaryAssistedT)
		}
	}
	g.Mint(ic, primary, big.NewInt(int64(netFee)), false)
	return nil
}

// PostPersist implements the Contract interface.
func (g *Lub) PostPersist(ic *interop.Context) error {
	return nil
}

// getVitaExemptType determines if sender is exempt from fees and what type.
func (g *Lub) getVitaExemptType(d *dao.Simple, sender util.Uint160) VitaExemptType {
	// Check local Vita first
	if g.Vita != nil {
		token, err := g.Vita.GetTokenByOwner(d, sender)
		if err == nil && token != nil && token.Status == state.TokenStatusActive {
			return VitaExemptLocal
		}
	}

	// Check naturalized citizen (treated same as local)
	if g.Federation != nil && g.Federation.IsNaturalizedCitizen(d, sender) {
		return VitaExemptLocal
	}

	// Check visitor registry
	if g.Federation != nil && g.Federation.IsVisitor(d, sender) {
		return VitaExemptVisitor
	}

	// Check asylum registry (humanitarian override)
	// Asylum seekers get fee exemption even if their home chain revoked their Vita
	if g.Federation != nil && g.Federation.HasAsylum(d, sender) {
		return VitaExemptAsylum
	}

	return VitaExemptNone
}

// burnFromTreasury burns Lub from Treasury for local Vita holders (100% local).
func (g *Lub) burnFromTreasury(ic *interop.Context, amount *big.Int) {
	g.burnFromTreasuryInternal(ic, amount, 100, 0)
}

// burnFromTreasuryWithSplit burns Lub with visiting fee split between local Treasury and inter-chain debt.
func (g *Lub) burnFromTreasuryWithSplit(ic *interop.Context, amount *big.Int, homeChain uint32) {
	localPercent := uint8(100)
	if g.Federation != nil {
		localPercent = g.Federation.GetVisitingFeePercent(ic.DAO)
	}
	g.burnFromTreasuryInternal(ic, amount, localPercent, homeChain)
}

// burnFromTreasuryInternal handles the actual burn with optional split between local and inter-chain.
func (g *Lub) burnFromTreasuryInternal(ic *interop.Context, amount *big.Int, localPercent uint8, homeChain uint32) {
	if g.Treasury == nil {
		return
	}

	treasuryAddr := g.Treasury.Address()
	treasuryBalance := g.BalanceOf(ic.DAO, treasuryAddr)

	// Calculate local vs inter-chain portions
	localAmount := new(big.Int).Mul(amount, big.NewInt(int64(localPercent)))
	localAmount.Div(localAmount, big.NewInt(100))
	interChainAmount := new(big.Int).Sub(amount, localAmount)

	// Burn from Treasury what we can
	if treasuryBalance.Cmp(localAmount) >= 0 {
		g.Burn(ic, treasuryAddr, localAmount)
	} else if treasuryBalance.Sign() > 0 {
		deficit := new(big.Int).Sub(localAmount, treasuryBalance)
		g.Burn(ic, treasuryAddr, treasuryBalance)
		g.Treasury.AddDeficit(ic.DAO, deficit)
	} else {
		g.Treasury.AddDeficit(ic.DAO, localAmount)
	}

	// Record inter-chain debt (if visitor)
	if interChainAmount.Sign() > 0 && homeChain != 0 && g.Federation != nil {
		g.Federation.AddInterChainDebt(ic.DAO, homeChain, interChainAmount)
	}
}

// ActiveIn implements the Contract interface.
func (g *Lub) ActiveIn() *config.Hardfork {
	return nil
}

// BalanceOf returns native Lub token balance for the acc.
func (g *Lub) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
	return g.balanceOfInternal(d, acc)
}

func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) {
	cfg := ic.Chain.GetConfig()
	committee, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
	if err != nil {
		return util.Uint160{}, err
	}
	s, err := smartcontract.CreateDefaultMultiSigRedeemScript(committee[:cfg.GetNumOfCNs(0)])
	if err != nil {
		return util.Uint160{}, err
	}
	return hash.Hash160(s), nil
}
