package light_client

import (
	"fmt"

	fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
	"github.com/OffchainLabs/prysm/v6/config/params"
	consensustypes "github.com/OffchainLabs/prysm/v6/consensus-types"
	"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
	"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
	pb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
	"github.com/OffchainLabs/prysm/v6/runtime/version"
	"github.com/OffchainLabs/prysm/v6/time/slots"
	"google.golang.org/protobuf/proto"
)

func NewWrappedHeader(m proto.Message) (interfaces.LightClientHeader, error) {
	if m == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	switch t := m.(type) {
	case *pb.LightClientHeaderAltair:
		return NewWrappedHeaderAltair(t)
	case *pb.LightClientHeaderCapella:
		return NewWrappedHeaderCapella(t)
	case *pb.LightClientHeaderDeneb:
		if slots.ToEpoch(t.Beacon.Slot) >= params.BeaconConfig().ElectraForkEpoch {
			return NewWrappedHeaderElectra(t)
		}
		return NewWrappedHeaderDeneb(t)
	default:
		return nil, fmt.Errorf("cannot construct light client header from type %T", t)
	}
}

type headerAltair struct {
	p *pb.LightClientHeaderAltair
}

var _ interfaces.LightClientHeader = &headerAltair{}

func NewWrappedHeaderAltair(p *pb.LightClientHeaderAltair) (interfaces.LightClientHeader, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	return &headerAltair{p: p}, nil
}

func (h *headerAltair) MarshalSSZTo(dst []byte) ([]byte, error) {
	return h.p.MarshalSSZTo(dst)
}

func (h *headerAltair) MarshalSSZ() ([]byte, error) {
	return h.p.MarshalSSZ()
}

func (h *headerAltair) SizeSSZ() int {
	return h.p.SizeSSZ()
}

func (h *headerAltair) Proto() proto.Message {
	return h.p
}

func (h *headerAltair) Version() int {
	return version.Altair
}

func (h *headerAltair) Beacon() *pb.BeaconBlockHeader {
	return h.p.Beacon
}

func (h *headerAltair) Execution() (interfaces.ExecutionData, error) {
	return nil, consensustypes.ErrNotSupported("Execution", h.Version())
}

func (h *headerAltair) ExecutionBranch() (interfaces.LightClientExecutionBranch, error) {
	return interfaces.LightClientExecutionBranch{}, consensustypes.ErrNotSupported("ExecutionBranch", h.Version())
}

type headerCapella struct {
	p               *pb.LightClientHeaderCapella
	execution       interfaces.ExecutionData
	executionBranch interfaces.LightClientExecutionBranch
}

var _ interfaces.LightClientHeader = &headerCapella{}

func NewWrappedHeaderCapella(p *pb.LightClientHeaderCapella) (interfaces.LightClientHeader, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	execution, err := blocks.WrappedExecutionPayloadHeaderCapella(p.Execution)
	if err != nil {
		return nil, err
	}

	branch, err := createBranch[interfaces.LightClientExecutionBranch](
		"execution",
		p.ExecutionBranch,
		fieldparams.ExecutionBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &headerCapella{
		p:               p,
		execution:       execution,
		executionBranch: branch,
	}, nil
}

func (h *headerCapella) MarshalSSZTo(dst []byte) ([]byte, error) {
	return h.p.MarshalSSZTo(dst)
}

func (h *headerCapella) MarshalSSZ() ([]byte, error) {
	return h.p.MarshalSSZ()
}

func (h *headerCapella) SizeSSZ() int {
	return h.p.SizeSSZ()
}

func (h *headerCapella) Proto() proto.Message {
	return h.p
}

func (h *headerCapella) Version() int {
	return version.Capella
}

func (h *headerCapella) Beacon() *pb.BeaconBlockHeader {
	return h.p.Beacon
}

func (h *headerCapella) Execution() (interfaces.ExecutionData, error) {
	return h.execution, nil
}

func (h *headerCapella) ExecutionBranch() (interfaces.LightClientExecutionBranch, error) {
	return h.executionBranch, nil
}

type headerDeneb struct {
	p               *pb.LightClientHeaderDeneb
	execution       interfaces.ExecutionData
	executionBranch interfaces.LightClientExecutionBranch
}

var _ interfaces.LightClientHeader = &headerDeneb{}

func NewWrappedHeaderDeneb(p *pb.LightClientHeaderDeneb) (interfaces.LightClientHeader, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	execution, err := blocks.WrappedExecutionPayloadHeaderDeneb(p.Execution)
	if err != nil {
		return nil, err
	}

	branch, err := createBranch[interfaces.LightClientExecutionBranch](
		"execution",
		p.ExecutionBranch,
		fieldparams.ExecutionBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &headerDeneb{
		p:               p,
		execution:       execution,
		executionBranch: branch,
	}, nil
}

func (h *headerDeneb) MarshalSSZTo(dst []byte) ([]byte, error) {
	return h.p.MarshalSSZTo(dst)
}

func (h *headerDeneb) MarshalSSZ() ([]byte, error) {
	return h.p.MarshalSSZ()
}

func (h *headerDeneb) SizeSSZ() int {
	return h.p.SizeSSZ()
}

func (h *headerDeneb) Proto() proto.Message {
	return h.p
}

func (h *headerDeneb) Version() int {
	return version.Deneb
}

func (h *headerDeneb) Beacon() *pb.BeaconBlockHeader {
	return h.p.Beacon
}

func (h *headerDeneb) Execution() (interfaces.ExecutionData, error) {
	return h.execution, nil
}

func (h *headerDeneb) ExecutionBranch() (interfaces.LightClientExecutionBranch, error) {
	return h.executionBranch, nil
}

type headerElectra struct {
	p               *pb.LightClientHeaderDeneb
	execution       interfaces.ExecutionData
	executionBranch interfaces.LightClientExecutionBranch
}

var _ interfaces.LightClientHeader = &headerElectra{}

func NewWrappedHeaderElectra(p *pb.LightClientHeaderDeneb) (interfaces.LightClientHeader, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	execution, err := blocks.WrappedExecutionPayloadHeaderDeneb(p.Execution)
	if err != nil {
		return nil, err
	}

	branch, err := createBranch[interfaces.LightClientExecutionBranch](
		"execution",
		p.ExecutionBranch,
		fieldparams.ExecutionBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &headerElectra{
		p:               p,
		execution:       execution,
		executionBranch: branch,
	}, nil
}

func (h *headerElectra) MarshalSSZTo(dst []byte) ([]byte, error) {
	return h.p.MarshalSSZTo(dst)
}

func (h *headerElectra) MarshalSSZ() ([]byte, error) {
	return h.p.MarshalSSZ()
}

func (h *headerElectra) SizeSSZ() int {
	return h.p.SizeSSZ()
}

func (h *headerElectra) Proto() proto.Message {
	return h.p
}

func (h *headerElectra) Version() int {
	return version.Electra
}

func (h *headerElectra) Beacon() *pb.BeaconBlockHeader {
	return h.p.Beacon
}

func (h *headerElectra) Execution() (interfaces.ExecutionData, error) {
	return h.execution, nil
}

func (h *headerElectra) ExecutionBranch() (interfaces.LightClientExecutionBranch, error) {
	return h.executionBranch, nil
}
