package sources

import (
	"context"
	"errors"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/exchain/go-exchain/exchain"
	nebulav1 "github.com/exchain/go-exchain/exchain/protocol/gen/go/nebula/v1"
	"time"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/eth/catalyst"
	"github.com/ethereum/go-ethereum/log"
	"github.com/ethereum/go-ethereum/params"

	"github.com/exchain/go-exchain/op-node/rollup"
	"github.com/exchain/go-exchain/op-service/client"
	"github.com/exchain/go-exchain/op-service/eth"
	"github.com/exchain/go-exchain/op-service/sources/caching"
)

type EngineClientConfig struct {
	L2ClientConfig
}

func EngineClientDefaultConfig(config *rollup.Config) *EngineClientConfig {
	return &EngineClientConfig{
		// engine is trusted, no need to recompute responses etc.
		L2ClientConfig: *L2ClientDefaultConfig(config, true),
	}
}

// EngineClient extends L2Client with engine API bindings.
type EngineClient struct {
	*L2Client
	*EngineAPIClient
}

func (e *EngineClient) Close() {

}

func (e *EngineClient) NewPayload(params exchain.PayloadParams) (exchain.ExecutionResult, error) {
	return exchain.ExecutionResult{}, errors.New("not implemented")
}

func (e *EngineClient) ProcessPayload(block *nebulav1.Block) error {
	return errors.New("not implemented")
}

func (e *EngineClient) PayloadByHash(ctx context.Context, hash common.Hash) (*eth.ExecutionPayloadEnvelope, error) {
	return nil, errors.New("not implemented")
}

func (e *EngineClient) PayloadByNumber(ctx context.Context, u uint64) (*eth.ExecutionPayloadEnvelope, error) {
	return nil, errors.New("not implemented")
}

func (e *EngineClient) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) {
	return eth.L2BlockRef{}, errors.New("not implemented")
}

func (e *EngineClient) BlockRefByNumber(ctx context.Context, num uint64) (eth.BlockRef, error) {
	return eth.BlockRef{}, errors.New("not implemented")
}

func (e *EngineClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
	return nil, types.Receipts{}, errors.New("not implemented")
}

func NewEngineClient(client client.RPC, log log.Logger, metrics caching.Metrics, config *EngineClientConfig) (*EngineClient, error) {
	l2Client, err := NewL2Client(client, log, metrics, &config.L2ClientConfig)
	if err != nil {
		return nil, err
	}

	engineAPIClient := NewEngineAPIClient(client, log)

	return &EngineClient{
		L2Client:        l2Client,
		EngineAPIClient: engineAPIClient,
	}, nil
}

// EngineAPIClient is an RPC client for the Engine API functions.
type EngineAPIClient struct {
	RPC     client.RPC
	log     log.Logger
	timeout time.Duration
}

func NewEngineAPIClient(rpc client.RPC, l log.Logger) *EngineAPIClient {
	return &EngineAPIClient{
		RPC:     rpc,
		log:     l,
		timeout: time.Second * 5,
	}
}

func NewEngineAPIClientWithTimeout(rpc client.RPC, l log.Logger, timeout time.Duration) *EngineAPIClient {
	return &EngineAPIClient{
		RPC:     rpc,
		log:     l,
		timeout: timeout,
	}
}

// NewPayload executes a full block on the execution engine.
// This returns a PayloadStatusV1 which encodes any validation/processing error,
// and this type of error is kept separate from the returned `error` used for RPC errors, like timeouts.
func (s *EngineAPIClient) NewPayload(ctx context.Context, payload *eth.ExecutionPayload, parentBeaconBlockRoot *common.Hash) (*eth.PayloadStatusV1, error) {
	e := s.log.New("block_hash", payload.BlockHash)
	e.Trace("sending payload for execution")
	return nil, errors.New("not implemented")
}

// GetPayload gets the execution payload associated with the PayloadId.
// It's the caller's responsibility to check the error type, and in case of an rpc.Error, check the ErrorCode.
func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.PayloadInfo) (*eth.ExecutionPayloadEnvelope, error) {
	e := s.log.New("payload_id", payloadInfo.ID)
	e.Trace("getting payload")
	return nil, errors.New("not implemented")
}

func (s *EngineAPIClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) {
	var result params.ProtocolVersion
	err := s.RPC.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{
		Recommended: recommended,
		Required:    required,
	})
	return result, err
}
