package alphabet

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/metrics"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/panjf2000/ants/v2"
	"go.uber.org/zap"
)

type (
	// Indexer is a callback interface for inner ring global state.
	Indexer interface {
		AlphabetIndex() int
	}

	// Contracts is an interface of the storage
	// of the alphabet contract addresses.
	Contracts interface {
		// GetByIndex must return the address of the
		// alphabet contract by index of the glagolitic
		// letter (e.g 0 for Az, 40 for Izhitsa).
		//
		// Must return false if the index does not
		// match any alphabet contract.
		GetByIndex(int) (util.Uint160, bool)
	}

	netmapClient interface {
		NetMap() (*netmap.NetMap, error)
	}

	morphClient interface {
		Invoke(contract util.Uint160, fee fixedn.Fixed8, method string, args ...any) (uint32, error)
		TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error
		BatchTransferGas(receivers []util.Uint160, amount fixedn.Fixed8) error
	}

	// Processor of events produced for alphabet contracts in the sidechain.
	Processor struct {
		parsedWallets []util.Uint160
		// protects parsedWallets from concurrent change
		pwLock            sync.RWMutex
		log               *logger.Logger
		metrics           metrics.Register
		pool              *ants.Pool
		alphabetContracts Contracts
		netmapClient      netmapClient
		morphClient       morphClient
		irList            Indexer
		storageEmission   uint64
	}

	// Params of the processor constructor.
	Params struct {
		ParsedWallets     []util.Uint160
		Log               *logger.Logger
		Metrics           metrics.Register
		PoolSize          int
		AlphabetContracts Contracts
		NetmapClient      netmapClient
		MorphClient       morphClient
		IRList            Indexer
		StorageEmission   uint64
	}
)

// New creates a frostfs mainnet contract processor instance.
func New(p *Params) (*Processor, error) {
	switch {
	case p.Log == nil:
		return nil, errors.New("ir/alphabet: logger is not set")
	case p.MorphClient == nil:
		return nil, errors.New("ir/alphabet: neo:morph client is not set")
	case p.IRList == nil:
		return nil, errors.New("ir/alphabet: global state is not set")
	}

	p.Log.Debug(logs.AlphabetAlphabetWorkerPool, zap.Int("size", p.PoolSize))

	pool, err := ants.NewPool(p.PoolSize, ants.WithNonblocking(true))
	if err != nil {
		return nil, fmt.Errorf("ir/frostfs: can't create worker pool: %w", err)
	}

	metricsRegister := p.Metrics
	if metricsRegister == nil {
		metricsRegister = metrics.DefaultRegister{}
	}

	return &Processor{
		parsedWallets:     p.ParsedWallets,
		log:               p.Log,
		metrics:           metricsRegister,
		pool:              pool,
		alphabetContracts: p.AlphabetContracts,
		netmapClient:      p.NetmapClient,
		morphClient:       p.MorphClient,
		irList:            p.IRList,
		storageEmission:   p.StorageEmission,
	}, nil
}

func (ap *Processor) SetParsedWallets(parsedWallets []util.Uint160) {
	ap.pwLock.Lock()
	ap.parsedWallets = parsedWallets
	ap.pwLock.Unlock()
}

// ListenerNotificationParsers for the 'event.Listener' event producer.
func (ap *Processor) ListenerNotificationParsers() []event.NotificationParserInfo {
	return nil
}

// ListenerNotificationHandlers for the 'event.Listener' event producer.
func (ap *Processor) ListenerNotificationHandlers() []event.NotificationHandlerInfo {
	return nil
}

// ListenerNotaryParsers for the 'event.Listener' event producer.
func (ap *Processor) ListenerNotaryParsers() []event.NotaryParserInfo {
	return nil
}

// ListenerNotaryHandlers for the 'event.Listener' event producer.
func (ap *Processor) ListenerNotaryHandlers() []event.NotaryHandlerInfo {
	return nil
}

// WaitPoolRunning waits while pool has running tasks
// For use in test only.
func (ap *Processor) WaitPoolRunning() {
	for ap.pool.Running() > 0 {
		time.Sleep(10 * time.Millisecond)
	}
}