package neofs

import (
	"sync"

	lru "github.com/hashicorp/golang-lru"
	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neofs-node/pkg/morph/client"
	"github.com/nspcc-dev/neofs-node/pkg/morph/event"
	neofsEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/neofs"
	"github.com/panjf2000/ants/v2"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

type (
	// EpochState is a callback interface for inner ring global state
	EpochState interface {
		EpochCounter() uint64
	}

	// ActiveState is a callback interface for inner ring global state
	ActiveState interface {
		IsActive() bool
	}

	// PrecisionConverter converts balance amount values.
	PrecisionConverter interface {
		ToBalancePrecision(int64) int64
	}

	// Processor of events produced by neofs contract in main net.
	Processor struct {
		log                 *zap.Logger
		pool                *ants.Pool
		neofsContract       util.Uint160
		balanceContract     util.Uint160
		netmapContract      util.Uint160
		morphClient         *client.Client
		epochState          EpochState
		activeState         ActiveState
		converter           PrecisionConverter
		mintEmitLock        *sync.Mutex
		mintEmitCache       *lru.Cache
		mintEmitThreshold   uint64
		mintEmitValue       fixedn.Fixed8
		gasBalanceThreshold int64
	}

	// Params of the processor constructor.
	Params struct {
		Log                 *zap.Logger
		PoolSize            int
		NeoFSContract       util.Uint160
		BalanceContract     util.Uint160
		NetmapContract      util.Uint160
		MorphClient         *client.Client
		EpochState          EpochState
		ActiveState         ActiveState
		Converter           PrecisionConverter
		MintEmitCacheSize   int
		MintEmitThreshold   uint64 // in epochs
		MintEmitValue       fixedn.Fixed8
		GasBalanceThreshold int64
	}
)

const (
	depositNotification  = "Deposit"
	withdrawNotification = "Withdraw"
	chequeNotification   = "Cheque"
	configNotification   = "SetConfig"
	updateIRNotification = "InnerRingUpdate"
)

// New creates neofs mainnet contract processor instance.
func New(p *Params) (*Processor, error) {
	switch {
	case p.Log == nil:
		return nil, errors.New("ir/neofs: logger is not set")
	case p.MorphClient == nil:
		return nil, errors.New("ir/neofs: neo:morph client is not set")
	case p.EpochState == nil:
		return nil, errors.New("ir/neofs: global state is not set")
	case p.ActiveState == nil:
		return nil, errors.New("ir/neofs: global state is not set")
	case p.Converter == nil:
		return nil, errors.New("ir/neofs: balance precision converter is not set")
	}

	p.Log.Debug("neofs worker pool", zap.Int("size", p.PoolSize))

	pool, err := ants.NewPool(p.PoolSize, ants.WithNonblocking(true))
	if err != nil {
		return nil, errors.Wrap(err, "ir/neofs: can't create worker pool")
	}

	lruCache, err := lru.New(p.MintEmitCacheSize)
	if err != nil {
		return nil, errors.Wrap(err, "ir/neofs: can't create LRU cache for gas emission")
	}

	return &Processor{
		log:                 p.Log,
		pool:                pool,
		neofsContract:       p.NeoFSContract,
		balanceContract:     p.BalanceContract,
		netmapContract:      p.NetmapContract,
		morphClient:         p.MorphClient,
		epochState:          p.EpochState,
		activeState:         p.ActiveState,
		converter:           p.Converter,
		mintEmitLock:        new(sync.Mutex),
		mintEmitCache:       lruCache,
		mintEmitThreshold:   p.MintEmitThreshold,
		mintEmitValue:       p.MintEmitValue,
		gasBalanceThreshold: p.GasBalanceThreshold,
	}, nil
}

// ListenerParsers for the 'event.Listener' event producer.
func (np *Processor) ListenerParsers() []event.ParserInfo {
	var parsers []event.ParserInfo

	// deposit event
	deposit := event.ParserInfo{}
	deposit.SetType(depositNotification)
	deposit.SetScriptHash(np.neofsContract)
	deposit.SetParser(neofsEvent.ParseDeposit)
	parsers = append(parsers, deposit)

	// withdraw event
	withdraw := event.ParserInfo{}
	withdraw.SetType(withdrawNotification)
	withdraw.SetScriptHash(np.neofsContract)
	withdraw.SetParser(neofsEvent.ParseWithdraw)
	parsers = append(parsers, withdraw)

	// cheque event
	cheque := event.ParserInfo{}
	cheque.SetType(chequeNotification)
	cheque.SetScriptHash(np.neofsContract)
	cheque.SetParser(neofsEvent.ParseCheque)
	parsers = append(parsers, cheque)

	// config event
	config := event.ParserInfo{}
	config.SetType(configNotification)
	config.SetScriptHash(np.neofsContract)
	config.SetParser(neofsEvent.ParseConfig)
	parsers = append(parsers, config)

	// update inner ring event
	updateIR := event.ParserInfo{}
	updateIR.SetType(updateIRNotification)
	updateIR.SetScriptHash(np.neofsContract)
	updateIR.SetParser(neofsEvent.ParseUpdateInnerRing)
	parsers = append(parsers, updateIR)

	return parsers
}

// ListenerHandlers for the 'event.Listener' event producer.
func (np *Processor) ListenerHandlers() []event.HandlerInfo {
	var handlers []event.HandlerInfo

	// deposit handler
	deposit := event.HandlerInfo{}
	deposit.SetType(depositNotification)
	deposit.SetScriptHash(np.neofsContract)
	deposit.SetHandler(np.handleDeposit)
	handlers = append(handlers, deposit)

	// withdraw handler
	withdraw := event.HandlerInfo{}
	withdraw.SetType(withdrawNotification)
	withdraw.SetScriptHash(np.neofsContract)
	withdraw.SetHandler(np.handleWithdraw)
	handlers = append(handlers, withdraw)

	// cheque handler
	cheque := event.HandlerInfo{}
	cheque.SetType(chequeNotification)
	cheque.SetScriptHash(np.neofsContract)
	cheque.SetHandler(np.handleCheque)
	handlers = append(handlers, cheque)

	// config handler
	config := event.HandlerInfo{}
	config.SetType(configNotification)
	config.SetScriptHash(np.neofsContract)
	config.SetHandler(np.handleConfig)
	handlers = append(handlers, config)

	// updateIR handler
	updateIR := event.HandlerInfo{}
	updateIR.SetType(updateIRNotification)
	updateIR.SetScriptHash(np.neofsContract)
	updateIR.SetHandler(np.handleUpdateInnerRing)
	handlers = append(handlers, updateIR)

	return handlers
}

// TimersHandlers for the 'Timers' event producer.
func (np *Processor) TimersHandlers() []event.HandlerInfo {
	return nil
}