// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package txpool

import (
	"context"

	"github.com/c2h5oh/datasize"
	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/common/log/v3"
	"github.com/erigontech/erigon/db/kv"
	"github.com/erigontech/erigon/db/kv/dbcfg"
	"github.com/erigontech/erigon/db/kv/kvcache"
	"github.com/erigontech/erigon/db/kv/mdbx"
	"github.com/erigontech/erigon/node/gointerfaces/remoteproto"
	"github.com/erigontech/erigon/node/gointerfaces/sentryproto"
	"github.com/erigontech/erigon/node/gointerfaces/txpoolproto"
	"github.com/erigontech/erigon/txnprovider/txpool/txpoolcfg"
)

func Assemble(
	ctx context.Context,
	cfg txpoolcfg.Config,
	chainDB kv.TemporalRoDB,
	cache kvcache.Cache,
	sentryClients []sentryproto.SentryClient,
	stateChangesClient StateChangesClient,
	builderNotifyNewTxns func(),
	logger log.Logger,
	ethBackend remoteproto.ETHBACKENDClient,
	opts ...Option,
) (*TxPool, txpoolproto.TxpoolServer, error) {
	options := applyOpts(opts...)
	poolDB, err := options.poolDBInitializer(ctx, cfg, logger)
	if err != nil {
		return nil, nil, err
	}

	chainConfig, _, err := SaveChainConfigIfNeed(ctx, chainDB, poolDB, logger)
	if err != nil {
		return nil, nil, err
	}

	chainID, _ := uint256.FromBig(chainConfig.ChainID)

	newTxns := make(chan Announcements, 1024)
	newSlotsStreams := &NewSlotsStreams{}
	pool, err := New(
		ctx,
		newTxns,
		poolDB,
		chainDB,
		cfg,
		cache,
		chainConfig,
		sentryClients,
		stateChangesClient,
		builderNotifyNewTxns,
		newSlotsStreams,
		ethBackend,
		logger,
		opts...,
	)
	if err != nil {
		return nil, nil, err
	}

	grpcServer := NewGrpcServer(ctx, pool, poolDB, newSlotsStreams, *chainID, logger)
	return pool, grpcServer, nil
}

type poolDBInitializer func(ctx context.Context, cfg txpoolcfg.Config, logger log.Logger) (kv.RwDB, error)

var defaultPoolDBInitializer = func(ctx context.Context, cfg txpoolcfg.Config, logger log.Logger) (kv.RwDB, error) {
	opts := mdbx.New(dbcfg.TxPoolDB, logger).
		Path(cfg.DBDir).
		WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { return kv.TxpoolTablesCfg }).
		WriteMergeThreshold(3 * 8192).
		PageSize(16 * datasize.KB).
		GrowthStep(16 * datasize.MB).
		DirtySpace(uint64(64 * datasize.MB)).
		MapSize(1 * datasize.TB).
		WriteMap(cfg.MdbxWriteMap)
	if cfg.MdbxPageSize > 0 {
		opts = opts.PageSize(cfg.MdbxPageSize)
	}
	if cfg.MdbxDBSizeLimit > 0 {
		opts = opts.MapSize(cfg.MdbxDBSizeLimit)
	}
	if cfg.MdbxGrowthStep > 0 {
		opts = opts.GrowthStep(cfg.MdbxGrowthStep)
	}
	return opts.Open(ctx)
}
