// 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 jsonrpc

import (
	"context"
	"fmt"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/db/kv"
	"github.com/erigontech/erigon/execution/protocol/rules"
	"github.com/erigontech/erigon/execution/types/accounts"
	"github.com/erigontech/erigon/polygon/bor"
	"github.com/erigontech/erigon/polygon/heimdall"
	"github.com/erigontech/erigon/rpc"
)

// BorAPI Bor specific routines
type BorAPI interface {
	GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error)
	GetAuthor(blockNrOrHash *rpc.BlockNumberOrHash) (accounts.Address, error)
	GetSnapshotAtHash(hash common.Hash) (*Snapshot, error)
	GetSigners(number *rpc.BlockNumber) ([]common.Address, error)
	GetSignersAtHash(hash common.Hash) ([]common.Address, error)
	GetCurrentProposer() (common.Address, error)
	GetCurrentValidators() ([]*heimdall.Validator, error)
	GetSnapshotProposer(blockNrOrHash *rpc.BlockNumberOrHash) (common.Address, error)
	GetSnapshotProposerSequence(blockNrOrHash *rpc.BlockNumberOrHash) (BlockSigners, error)
	GetRootHash(start uint64, end uint64) (string, error)
}

type spanProducersReader interface {
	Producers(ctx context.Context, blockNum uint64) (*heimdall.ValidatorSet, error)
}

// BorImpl is implementation of the BorAPI interface
type BorImpl struct {
	*BaseAPI
	db                  kv.TemporalRoDB // the chain db
	spanProducersReader spanProducersReader
}

// NewBorAPI returns BorImpl instance
func NewBorAPI(base *BaseAPI, db kv.TemporalRoDB, spanProducersReader spanProducersReader) *BorImpl {
	return &BorImpl{
		BaseAPI:             base,
		db:                  db,
		spanProducersReader: spanProducersReader,
	}
}

func (api *BorImpl) bor() (*bor.Bor, error) {
	type lazy interface {
		HasEngine() bool
		Engine() rules.EngineReader
	}

	switch engine := api.engine().(type) {
	case *bor.Bor:
		return engine, nil
	case lazy:
		if engine.HasEngine() {
			if bor, ok := engine.Engine().(*bor.Bor); ok {
				return bor, nil
			}
		}
	}

	return nil, fmt.Errorf("unknown or invalid rules engine: %T", api.engine())
}
