package graph

// This file will be automatically regenerated based on the schema, any resolver
// implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.94

import (
	"context"
	"fmt"
	"math/big"
	"strconv"
	"strings"

	"github.com/erigontech/erigon/cmd/rpcdaemon/graphql/graph/model"
	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/hexutil"
	"github.com/erigontech/erigon/rpc"
	"github.com/erigontech/erigon/rpc/filters"
)

// Storage is the resolver for the storage field.
func (r *accountResolver) Storage(ctx context.Context, obj *model.Account, slot string) (string, error) {
	if !common.IsHexAddress(obj.Address) {
		return "", fmt.Errorf("invalid account address: %s", obj.Address)
	}
	if len(slot) != 66 || slot[:2] != "0x" {
		return "", fmt.Errorf("slot must be a 0x-prefixed 32-byte hex string, got %q", slot)
	}
	addr := common.HexToAddress(obj.Address)
	return r.GraphQLAPI.GetAccountStorage(ctx, addr, slot, rpc.BlockNumber(obj.BlockNum))
}

// TransactionAt is the resolver for the transactionAt field.
func (r *blockResolver) TransactionAt(ctx context.Context, obj *model.Block, index int) (*model.Transaction, error) {
	if index < 0 || index >= len(obj.Transactions) {
		return nil, nil
	}
	return obj.Transactions[index], nil
}

// Logs is the resolver for the logs field.
func (r *blockResolver) Logs(ctx context.Context, obj *model.Block, filter model.BlockFilterCriteria) ([]*model.Log, error) {
	blockHash := common.HexToHash(obj.Hash)
	crit := filters.FilterCriteria{BlockHash: &blockHash}
	var err error
	if crit.Addresses, err = addressesFromModel(filter.Addresses); err != nil {
		return nil, err
	}
	if crit.Topics, err = topicsFromModel(filter.Topics); err != nil {
		return nil, err
	}
	logs, err := r.GraphQLAPI.GetLogs(ctx, crit)
	if err != nil {
		return nil, err
	}
	return rpcLogsToModel(logs), nil
}

// Account is the resolver for the account field.
func (r *blockResolver) Account(ctx context.Context, obj *model.Block, address string) (*model.Account, error) {
	if !common.IsHexAddress(address) {
		return nil, fmt.Errorf("invalid address %q", address)
	}
	return r.resolveAccountAtBlock(ctx, address, obj.Number, nil)
}

// Call is the resolver for the call field.
func (r *blockResolver) Call(ctx context.Context, obj *model.Block, data model.CallData) (*model.CallResult, error) {
	args, err := callDataToArgs(data)
	if err != nil {
		return nil, err
	}
	res, err := r.GraphQLAPI.Call(ctx, rpc.BlockNumber(obj.Number), args)
	if err != nil {
		return nil, err
	}
	return &model.CallResult{
		Data:    hexutil.Encode(res.Data),
		GasUsed: res.GasUsed,
		Status:  res.Status,
	}, nil
}

// EstimateGas is the resolver for the estimateGas field.
func (r *blockResolver) EstimateGas(ctx context.Context, obj *model.Block, data model.CallData) (uint64, error) {
	args, err := callDataToArgs(data)
	if err != nil {
		return 0, err
	}
	return r.GraphQLAPI.EstimateGas(ctx, rpc.BlockNumber(obj.Number), args)
}

// SendRawTransaction is the resolver for the sendRawTransaction field.
func (r *mutationResolver) SendRawTransaction(ctx context.Context, data string) (string, error) {
	encodedTx, err := hexutil.Decode(data)
	if err != nil {
		return "", fmt.Errorf("invalid transaction data: %w", err)
	}
	hash, err := r.GraphQLAPI.SendRawTransaction(ctx, hexutil.Bytes(encodedTx))
	if err != nil {
		return "", err
	}
	return hash.Hex(), nil
}

// Account is the resolver for the account field.
func (r *pendingResolver) Account(ctx context.Context, obj *model.Pending, address string) (*model.Account, error) {
	if !common.IsHexAddress(address) {
		return nil, fmt.Errorf("invalid address %q", address)
	}
	addr := common.HexToAddress(address)
	balance, nonce, code, err := r.GraphQLAPI.GetAccountInfo(ctx, addr, rpc.PendingBlockNumber)
	if err != nil {
		return nil, err
	}
	pendingBlockNum := rpc.PendingBlockNumber
	return &model.Account{
		Address:          strings.ToLower(address),
		Balance:          balance,
		TransactionCount: nonce,
		Code:             code,
		BlockNum:         uint64(pendingBlockNum),
	}, nil
}

// Call is the resolver for the call field.
func (r *pendingResolver) Call(ctx context.Context, obj *model.Pending, data model.CallData) (*model.CallResult, error) {
	args, err := callDataToArgs(data)
	if err != nil {
		return nil, err
	}
	res, err := r.GraphQLAPI.Call(ctx, rpc.PendingBlockNumber, args)
	if err != nil {
		return nil, err
	}
	return &model.CallResult{
		Data:    hexutil.Encode(res.Data),
		GasUsed: res.GasUsed,
		Status:  res.Status,
	}, nil
}

// EstimateGas is the resolver for the estimateGas field.
func (r *pendingResolver) EstimateGas(ctx context.Context, obj *model.Pending, data model.CallData) (uint64, error) {
	args, err := callDataToArgs(data)
	if err != nil {
		return 0, err
	}
	return r.GraphQLAPI.EstimateGas(ctx, rpc.PendingBlockNumber, args)
}

// Block is the resolver for the block field.
func (r *queryResolver) Block(ctx context.Context, number *string, hash *string) (*model.Block, error) {
	if number != nil && hash != nil {
		return nil, &rpc.InvalidParamsError{Message: "Invalid params"}
	}

	if hash != nil {
		blockHash := common.HexToHash(*hash)
		res, err := r.GraphQLAPI.GetBlockDetailsByHash(ctx, blockHash)
		if err != nil {
			return nil, err
		}
		if res == nil {
			return nil, nil
		}
		return r.buildBlock(res)
	}

	var blockNumber rpc.BlockNumber

	if number != nil {
		bNum, err := strconv.ParseUint(*number, 10, 64)
		if err == nil {
			blockNumber = rpc.BlockNumber(bNum)
		} else {
			bNum, err := hexutil.DecodeUint64(*number)
			if err == nil {
				blockNumber = rpc.BlockNumber(bNum)
			} else {
				return nil, fmt.Errorf("invalid block number: %s", *number)
			}
		}
	} else {
		blockNumber = rpc.LatestBlockNumber
	}

	res, err := r.GraphQLAPI.GetBlockDetails(ctx, blockNumber)
	if err != nil {
		return nil, err
	}
	if res == nil {
		return nil, nil
	}
	return r.buildBlock(res)
}

// Blocks is the resolver for the blocks field.
func (r *queryResolver) Blocks(ctx context.Context, from uint64, to *uint64) ([]*model.Block, error) {
	fromBlockNumber := from

	var toBlockNumber uint64
	if to != nil {
		toBlockNumber = *to
	} else {
		latest, err := r.GraphQLAPI.GetLatestBlockNumber(ctx)
		if err != nil {
			return nil, err
		}
		toBlockNumber = latest
	}

	if toBlockNumber < fromBlockNumber {
		return nil, &rpc.InvalidParamsError{Message: "Invalid params"}
	}

	const maxBlocks = 25
	if toBlockNumber-fromBlockNumber+1 > maxBlocks {
		return nil, &rpc.InvalidParamsError{Message: "Invalid params"}
	}

	blocks := make([]*model.Block, 0, toBlockNumber-fromBlockNumber+1)
	for i := fromBlockNumber; i <= toBlockNumber; i++ {
		blockNumberStr := strconv.FormatUint(i, 10)
		block, err := r.Block(ctx, &blockNumberStr, nil)
		if err != nil {
			return nil, err
		}
		if block == nil {
			return nil, &rpc.InvalidParamsError{Message: "Invalid params"}
		}
		blocks = append(blocks, block)
	}

	return blocks, ctx.Err()
}

// Pending is the resolver for the pending field.
func (r *queryResolver) Pending(ctx context.Context) (*model.Pending, error) {
	txns, err := r.GraphQLAPI.GetPendingTransactions(ctx)
	if err != nil {
		return nil, err
	}
	transactions := make([]*model.Transaction, 0, len(txns))
	for _, txn := range txns {
		transactions = append(transactions, &model.Transaction{
			Nonce: hexutil.EncodeUint64(txn.GetNonce()),
			Gas:   txn.GetGasLimit(),
			Hash:  txn.Hash().Hex(),
		})
	}
	return &model.Pending{
		TransactionCount: uint64(len(txns)),
		Transactions:     transactions,
	}, nil
}

// Transaction is the resolver for the transaction field.
func (r *queryResolver) Transaction(ctx context.Context, hash string) (*model.Transaction, error) {
	hashBytes, decErr := hexutil.Decode(hash)
	if decErr != nil {
		return nil, fmt.Errorf("invalid transaction hash: %w", decErr)
	}
	txHash := common.BytesToHash(hashBytes)

	blockNum, ok, err := r.GraphQLAPI.GetBlockNumberForTx(ctx, txHash)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, nil
	}

	blockNumberStr := strconv.FormatUint(blockNum, 10)
	block, err := r.Block(ctx, &blockNumberStr, nil)
	if err != nil {
		return nil, err
	}
	if block == nil {
		return nil, nil
	}

	hashStr := txHash.String()
	for _, trans := range block.Transactions {
		if trans.Hash == hashStr {
			return trans, nil
		}
	}
	return nil, nil
}

// Logs is the resolver for the logs field.
func (r *queryResolver) Logs(ctx context.Context, filter model.FilterCriteria) ([]*model.Log, error) {
	var crit filters.FilterCriteria
	var err error
	if filter.FromBlock != nil {
		crit.FromBlock = new(big.Int).SetUint64(*filter.FromBlock)
	}
	if filter.ToBlock != nil {
		crit.ToBlock = new(big.Int).SetUint64(*filter.ToBlock)
	}
	if crit.Addresses, err = addressesFromModel(filter.Addresses); err != nil {
		return nil, err
	}
	if crit.Topics, err = topicsFromModel(filter.Topics); err != nil {
		return nil, err
	}
	logs, err := r.GraphQLAPI.GetLogs(ctx, crit)
	if err != nil {
		return nil, err
	}
	return rpcLogsToModel(logs), nil
}

// GasPrice is the resolver for the gasPrice field.
func (r *queryResolver) GasPrice(ctx context.Context) (string, error) {
	return r.GraphQLAPI.GasPrice(ctx)
}

// MaxPriorityFeePerGas is the resolver for the maxPriorityFeePerGas field.
func (r *queryResolver) MaxPriorityFeePerGas(ctx context.Context) (string, error) {
	panic("not implemented: MaxPriorityFeePerGas - maxPriorityFeePerGas")
}

// Syncing is the resolver for the syncing field.
func (r *queryResolver) Syncing(ctx context.Context) (*model.SyncState, error) {
	return nil, nil
}

// ChainID is the resolver for the chainID field.
func (r *queryResolver) ChainID(ctx context.Context) (string, error) {
	chainID, err := r.GraphQLAPI.GetChainID(ctx)

	return "0x" + strconv.FormatUint(chainID.Uint64(), 16), err
}

// From is the resolver for the from field.
func (r *transactionResolver) From(ctx context.Context, obj *model.Transaction, block *uint64) (*model.Account, error) {
	if obj.From == nil {
		return nil, nil
	}
	return r.resolveAccountAtBlock(ctx, obj.From.Address, obj.Block.Number, block)
}

// To is the resolver for the to field.
func (r *transactionResolver) To(ctx context.Context, obj *model.Transaction, block *uint64) (*model.Account, error) {
	if obj.To == nil {
		return nil, nil
	}
	return r.resolveAccountAtBlock(ctx, obj.To.Address, obj.Block.Number, block)
}

// Account returns AccountResolver implementation.
func (r *Resolver) Account() AccountResolver { return &accountResolver{r} }

// Block returns BlockResolver implementation.
func (r *Resolver) Block() BlockResolver { return &blockResolver{r} }

// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }

// Pending returns PendingResolver implementation.
func (r *Resolver) Pending() PendingResolver { return &pendingResolver{r} }

// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }

// Transaction returns TransactionResolver implementation.
func (r *Resolver) Transaction() TransactionResolver { return &transactionResolver{r} }

type (
	accountResolver     struct{ *Resolver }
	blockResolver       struct{ *Resolver }
	mutationResolver    struct{ *Resolver }
	pendingResolver     struct{ *Resolver }
	queryResolver       struct{ *Resolver }
	transactionResolver struct{ *Resolver }
)
