package light_client

import (
	"fmt"

	fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
	consensustypes "github.com/OffchainLabs/prysm/v6/consensus-types"
	"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
	"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
	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 NewWrappedUpdate(m proto.Message) (interfaces.LightClientUpdate, error) {
	if m == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}
	switch t := m.(type) {
	case *pb.LightClientUpdateAltair:
		return NewWrappedUpdateAltair(t)
	case *pb.LightClientUpdateCapella:
		return NewWrappedUpdateCapella(t)
	case *pb.LightClientUpdateDeneb:
		return NewWrappedUpdateDeneb(t)
	case *pb.LightClientUpdateElectra:
		return NewWrappedUpdateElectra(t)
	default:
		return nil, fmt.Errorf("cannot construct light client update from type %T", t)
	}
}

// In addition to the proto object being wrapped, we store some fields that have to be
// constructed from the proto, so that we don't have to reconstruct them every time
// in getters.

type updateAltair struct {
	p                       *pb.LightClientUpdateAltair
	attestedHeader          interfaces.LightClientHeader
	nextSyncCommitteeBranch interfaces.LightClientSyncCommitteeBranch
	finalizedHeader         interfaces.LightClientHeader
	finalityBranch          interfaces.LightClientFinalityBranch
}

var _ interfaces.LightClientUpdate = &updateAltair{}

func NewWrappedUpdateAltair(p *pb.LightClientUpdateAltair) (interfaces.LightClientUpdate, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}

	attestedHeader, err := NewWrappedHeader(p.AttestedHeader)
	if err != nil {
		return nil, err
	}

	var finalizedHeader interfaces.LightClientHeader
	if p.FinalizedHeader != nil {
		finalizedHeader, err = NewWrappedHeader(p.FinalizedHeader)
		if err != nil {
			return nil, err
		}
	}

	scBranch, err := createBranch[interfaces.LightClientSyncCommitteeBranch](
		"sync committee",
		p.NextSyncCommitteeBranch,
		fieldparams.SyncCommitteeBranchDepth,
	)
	if err != nil {
		return nil, err
	}
	finalityBranch, err := createBranch[interfaces.LightClientFinalityBranch](
		"finality",
		p.FinalityBranch,
		fieldparams.FinalityBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &updateAltair{
		p:                       p,
		attestedHeader:          attestedHeader,
		nextSyncCommitteeBranch: scBranch,
		finalizedHeader:         finalizedHeader,
		finalityBranch:          finalityBranch,
	}, nil
}

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

func (u *updateAltair) MarshalSSZ() ([]byte, error) {
	return u.p.MarshalSSZ()
}

func (u *updateAltair) SizeSSZ() int {
	return u.p.SizeSSZ()
}

func (u *updateAltair) Proto() proto.Message {
	return u.p
}

func (u *updateAltair) Version() int {
	return slots.ToForkVersion(u.attestedHeader.Beacon().Slot)
}

func (u *updateAltair) AttestedHeader() interfaces.LightClientHeader {
	return u.attestedHeader
}

func (u *updateAltair) SetAttestedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderAltair)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderAltair{})
	}
	u.p.AttestedHeader = p
	u.attestedHeader = header
	return nil
}

func (u *updateAltair) NextSyncCommittee() *pb.SyncCommittee {
	return u.p.NextSyncCommittee
}

func (u *updateAltair) SetNextSyncCommittee(sc *pb.SyncCommittee) {
	u.p.NextSyncCommittee = sc
}

func (u *updateAltair) NextSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) {
	return u.nextSyncCommitteeBranch, nil
}

func (u *updateAltair) SetNextSyncCommitteeBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientSyncCommitteeBranch]("sync committee", branch, fieldparams.SyncCommitteeBranchDepth)
	if err != nil {
		return err
	}
	u.nextSyncCommitteeBranch = b

	u.p.NextSyncCommitteeBranch = branch

	return nil
}

func (u *updateAltair) NextSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) {
	return [6][32]byte{}, consensustypes.ErrNotSupported("NextSyncCommitteeBranchElectra", version.Altair)
}

func (u *updateAltair) FinalizedHeader() interfaces.LightClientHeader {
	return u.finalizedHeader
}

func (u *updateAltair) SetFinalizedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderAltair)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderAltair{})
	}
	u.p.FinalizedHeader = p
	u.finalizedHeader = header
	return nil
}

func (u *updateAltair) FinalityBranch() (interfaces.LightClientFinalityBranch, error) {
	return u.finalityBranch, nil
}

func (u *updateAltair) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) {
	return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", version.Altair)
}

func (u *updateAltair) SetFinalityBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth)
	if err != nil {
		return err
	}
	u.finalityBranch = b
	u.p.FinalityBranch = branch
	return nil
}

func (u *updateAltair) SyncAggregate() *pb.SyncAggregate {
	return u.p.SyncAggregate
}

func (u *updateAltair) SetSyncAggregate(sa *pb.SyncAggregate) {
	u.p.SyncAggregate = sa
}

func (u *updateAltair) SignatureSlot() primitives.Slot {
	return u.p.SignatureSlot
}

func (u *updateAltair) SetSignatureSlot(slot primitives.Slot) {
	u.p.SignatureSlot = slot
}

// In addition to the proto object being wrapped, we store some fields that have to be
// constructed from the proto, so that we don't have to reconstruct them every time
// in getters.
type updateCapella struct {
	p                       *pb.LightClientUpdateCapella
	attestedHeader          interfaces.LightClientHeader
	nextSyncCommitteeBranch interfaces.LightClientSyncCommitteeBranch
	finalizedHeader         interfaces.LightClientHeader
	finalityBranch          interfaces.LightClientFinalityBranch
}

var _ interfaces.LightClientUpdate = &updateCapella{}

func NewWrappedUpdateCapella(p *pb.LightClientUpdateCapella) (interfaces.LightClientUpdate, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}

	attestedHeader, err := NewWrappedHeader(p.AttestedHeader)
	if err != nil {
		return nil, err
	}

	var finalizedHeader interfaces.LightClientHeader
	if p.FinalizedHeader != nil {
		finalizedHeader, err = NewWrappedHeader(p.FinalizedHeader)
		if err != nil {
			return nil, err
		}
	}

	scBranch, err := createBranch[interfaces.LightClientSyncCommitteeBranch](
		"sync committee",
		p.NextSyncCommitteeBranch,
		fieldparams.SyncCommitteeBranchDepth,
	)
	if err != nil {
		return nil, err
	}
	finalityBranch, err := createBranch[interfaces.LightClientFinalityBranch](
		"finality",
		p.FinalityBranch,
		fieldparams.FinalityBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &updateCapella{
		p:                       p,
		attestedHeader:          attestedHeader,
		nextSyncCommitteeBranch: scBranch,
		finalizedHeader:         finalizedHeader,
		finalityBranch:          finalityBranch,
	}, nil
}

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

func (u *updateCapella) MarshalSSZ() ([]byte, error) {
	return u.p.MarshalSSZ()
}

func (u *updateCapella) SizeSSZ() int {
	return u.p.SizeSSZ()
}

func (u *updateCapella) Proto() proto.Message {
	return u.p
}

func (u *updateCapella) Version() int {
	return slots.ToForkVersion(u.attestedHeader.Beacon().Slot)
}

func (u *updateCapella) AttestedHeader() interfaces.LightClientHeader {
	return u.attestedHeader
}

func (u *updateCapella) SetAttestedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderCapella)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderCapella{})
	}
	u.p.AttestedHeader = p
	u.attestedHeader = header
	return nil
}

func (u *updateCapella) NextSyncCommittee() *pb.SyncCommittee {
	return u.p.NextSyncCommittee
}

func (u *updateCapella) SetNextSyncCommittee(sc *pb.SyncCommittee) {
	u.p.NextSyncCommittee = sc
}

func (u *updateCapella) NextSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) {
	return u.nextSyncCommitteeBranch, nil
}

func (u *updateCapella) SetNextSyncCommitteeBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientSyncCommitteeBranch]("sync committee", branch, fieldparams.SyncCommitteeBranchDepth)
	if err != nil {
		return err
	}
	u.nextSyncCommitteeBranch = b

	u.p.NextSyncCommitteeBranch = branch

	return nil
}

func (u *updateCapella) NextSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) {
	return [6][32]byte{}, consensustypes.ErrNotSupported("NextSyncCommitteeBranchElectra", version.Capella)
}

func (u *updateCapella) FinalizedHeader() interfaces.LightClientHeader {
	return u.finalizedHeader
}

func (u *updateCapella) SetFinalizedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderCapella)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderCapella{})
	}
	u.p.FinalizedHeader = p
	u.finalizedHeader = header
	return nil
}

func (u *updateCapella) FinalityBranch() (interfaces.LightClientFinalityBranch, error) {
	return u.finalityBranch, nil
}

func (u *updateCapella) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) {
	return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version())
}

func (u *updateCapella) SetFinalityBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth)
	if err != nil {
		return err
	}
	u.finalityBranch = b
	u.p.FinalityBranch = branch
	return nil
}

func (u *updateCapella) SyncAggregate() *pb.SyncAggregate {
	return u.p.SyncAggregate
}

func (u *updateCapella) SetSyncAggregate(sa *pb.SyncAggregate) {
	u.p.SyncAggregate = sa
}

func (u *updateCapella) SignatureSlot() primitives.Slot {
	return u.p.SignatureSlot
}

func (u *updateCapella) SetSignatureSlot(slot primitives.Slot) {
	u.p.SignatureSlot = slot
}

// In addition to the proto object being wrapped, we store some fields that have to be
// constructed from the proto, so that we don't have to reconstruct them every time
// in getters.
type updateDeneb struct {
	p                       *pb.LightClientUpdateDeneb
	attestedHeader          interfaces.LightClientHeader
	nextSyncCommitteeBranch interfaces.LightClientSyncCommitteeBranch
	finalizedHeader         interfaces.LightClientHeader
	finalityBranch          interfaces.LightClientFinalityBranch
}

var _ interfaces.LightClientUpdate = &updateDeneb{}

func NewWrappedUpdateDeneb(p *pb.LightClientUpdateDeneb) (interfaces.LightClientUpdate, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}

	attestedHeader, err := NewWrappedHeader(p.AttestedHeader)
	if err != nil {
		return nil, err
	}

	var finalizedHeader interfaces.LightClientHeader
	if p.FinalizedHeader != nil {
		finalizedHeader, err = NewWrappedHeader(p.FinalizedHeader)
		if err != nil {
			return nil, err
		}
	}

	scBranch, err := createBranch[interfaces.LightClientSyncCommitteeBranch](
		"sync committee",
		p.NextSyncCommitteeBranch,
		fieldparams.SyncCommitteeBranchDepth,
	)
	if err != nil {
		return nil, err
	}
	finalityBranch, err := createBranch[interfaces.LightClientFinalityBranch](
		"finality",
		p.FinalityBranch,
		fieldparams.FinalityBranchDepth,
	)
	if err != nil {
		return nil, err
	}

	return &updateDeneb{
		p:                       p,
		attestedHeader:          attestedHeader,
		nextSyncCommitteeBranch: scBranch,
		finalizedHeader:         finalizedHeader,
		finalityBranch:          finalityBranch,
	}, nil
}

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

func (u *updateDeneb) MarshalSSZ() ([]byte, error) {
	return u.p.MarshalSSZ()
}

func (u *updateDeneb) SizeSSZ() int {
	return u.p.SizeSSZ()
}

func (u *updateDeneb) Proto() proto.Message {
	return u.p
}

func (u *updateDeneb) Version() int {
	return slots.ToForkVersion(u.attestedHeader.Beacon().Slot)
}

func (u *updateDeneb) AttestedHeader() interfaces.LightClientHeader {
	return u.attestedHeader
}

func (u *updateDeneb) SetAttestedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderDeneb)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{})
	}
	u.p.AttestedHeader = p
	u.attestedHeader = header
	return nil
}

func (u *updateDeneb) NextSyncCommittee() *pb.SyncCommittee {
	return u.p.NextSyncCommittee
}

func (u *updateDeneb) SetNextSyncCommittee(sc *pb.SyncCommittee) {
	u.p.NextSyncCommittee = sc
}

func (u *updateDeneb) NextSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) {
	return u.nextSyncCommitteeBranch, nil
}

func (u *updateDeneb) SetNextSyncCommitteeBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientSyncCommitteeBranch]("sync committee", branch, fieldparams.SyncCommitteeBranchDepth)
	if err != nil {
		return err
	}
	u.nextSyncCommitteeBranch = b

	u.p.NextSyncCommitteeBranch = branch

	return nil
}

func (u *updateDeneb) NextSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) {
	return [6][32]byte{}, consensustypes.ErrNotSupported("NextSyncCommitteeBranchElectra", version.Deneb)
}

func (u *updateDeneb) FinalizedHeader() interfaces.LightClientHeader {
	return u.finalizedHeader
}

func (u *updateDeneb) SetFinalizedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderDeneb)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{})
	}
	u.p.FinalizedHeader = p
	u.finalizedHeader = header
	return nil
}

func (u *updateDeneb) FinalityBranch() (interfaces.LightClientFinalityBranch, error) {
	return u.finalityBranch, nil
}

func (u *updateDeneb) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) {
	return interfaces.LightClientFinalityBranchElectra{}, consensustypes.ErrNotSupported("FinalityBranchElectra", u.Version())
}

func (u *updateDeneb) SetFinalityBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientFinalityBranch]("finality", branch, fieldparams.FinalityBranchDepth)
	if err != nil {
		return err
	}
	u.finalityBranch = b
	u.p.FinalityBranch = branch
	return nil
}

func (u *updateDeneb) SyncAggregate() *pb.SyncAggregate {
	return u.p.SyncAggregate
}

func (u *updateDeneb) SetSyncAggregate(sa *pb.SyncAggregate) {
	u.p.SyncAggregate = sa
}

func (u *updateDeneb) SignatureSlot() primitives.Slot {
	return u.p.SignatureSlot
}

func (u *updateDeneb) SetSignatureSlot(slot primitives.Slot) {
	u.p.SignatureSlot = slot
}

// In addition to the proto object being wrapped, we store some fields that have to be
// constructed from the proto, so that we don't have to reconstruct them every time
// in getters.
type updateElectra struct {
	p                       *pb.LightClientUpdateElectra
	attestedHeader          interfaces.LightClientHeader
	nextSyncCommitteeBranch interfaces.LightClientSyncCommitteeBranchElectra
	finalizedHeader         interfaces.LightClientHeader
	finalityBranch          interfaces.LightClientFinalityBranchElectra
}

var _ interfaces.LightClientUpdate = &updateElectra{}

func NewWrappedUpdateElectra(p *pb.LightClientUpdateElectra) (interfaces.LightClientUpdate, error) {
	if p == nil {
		return nil, consensustypes.ErrNilObjectWrapped
	}

	attestedHeader, err := NewWrappedHeader(p.AttestedHeader)
	if err != nil {
		return nil, err
	}

	var finalizedHeader interfaces.LightClientHeader
	if p.FinalizedHeader != nil {
		finalizedHeader, err = NewWrappedHeader(p.FinalizedHeader)
		if err != nil {
			return nil, err
		}
	}

	scBranch, err := createBranch[interfaces.LightClientSyncCommitteeBranchElectra](
		"sync committee",
		p.NextSyncCommitteeBranch,
		fieldparams.SyncCommitteeBranchDepthElectra,
	)
	if err != nil {
		return nil, err
	}

	finalityBranch, err := createBranch[interfaces.LightClientFinalityBranchElectra](
		"finality",
		p.FinalityBranch,
		fieldparams.FinalityBranchDepthElectra,
	)
	if err != nil {
		return nil, err
	}

	return &updateElectra{
		p:                       p,
		attestedHeader:          attestedHeader,
		nextSyncCommitteeBranch: scBranch,
		finalizedHeader:         finalizedHeader,
		finalityBranch:          finalityBranch,
	}, nil
}

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

func (u *updateElectra) MarshalSSZ() ([]byte, error) {
	return u.p.MarshalSSZ()
}

func (u *updateElectra) SizeSSZ() int {
	return u.p.SizeSSZ()
}

func (u *updateElectra) Proto() proto.Message {
	return u.p
}

func (u *updateElectra) Version() int {
	return slots.ToForkVersion(u.attestedHeader.Beacon().Slot)
}

func (u *updateElectra) AttestedHeader() interfaces.LightClientHeader {
	return u.attestedHeader
}

func (u *updateElectra) SetAttestedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderDeneb)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{})
	}
	u.p.AttestedHeader = p
	u.attestedHeader = header
	return nil
}

func (u *updateElectra) NextSyncCommittee() *pb.SyncCommittee {
	return u.p.NextSyncCommittee
}

func (u *updateElectra) SetNextSyncCommittee(sc *pb.SyncCommittee) {
	u.p.NextSyncCommittee = sc
}

func (u *updateElectra) NextSyncCommitteeBranch() (interfaces.LightClientSyncCommitteeBranch, error) {
	return [5][32]byte{}, consensustypes.ErrNotSupported("NextSyncCommitteeBranch", version.Electra)
}

func (u *updateElectra) SetNextSyncCommitteeBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientSyncCommitteeBranchElectra]("sync committee", branch, fieldparams.SyncCommitteeBranchDepthElectra)
	if err != nil {
		return err
	}
	u.nextSyncCommitteeBranch = b

	u.p.NextSyncCommitteeBranch = branch

	return nil
}

func (u *updateElectra) NextSyncCommitteeBranchElectra() (interfaces.LightClientSyncCommitteeBranchElectra, error) {
	return u.nextSyncCommitteeBranch, nil
}

func (u *updateElectra) FinalizedHeader() interfaces.LightClientHeader {
	return u.finalizedHeader
}

func (u *updateElectra) SetFinalizedHeader(header interfaces.LightClientHeader) error {
	p, ok := header.Proto().(*pb.LightClientHeaderDeneb)
	if !ok {
		return fmt.Errorf("header type %T is not %T", header.Proto(), &pb.LightClientHeaderDeneb{})
	}
	u.p.FinalizedHeader = p
	u.finalizedHeader = header
	return nil
}

func (u *updateElectra) FinalityBranch() (interfaces.LightClientFinalityBranch, error) {
	return interfaces.LightClientFinalityBranch{}, consensustypes.ErrNotSupported("FinalityBranch", u.Version())
}

func (u *updateElectra) FinalityBranchElectra() (interfaces.LightClientFinalityBranchElectra, error) {
	return u.finalityBranch, nil
}

func (u *updateElectra) SetFinalityBranch(branch [][]byte) error {
	b, err := createBranch[interfaces.LightClientFinalityBranchElectra]("finality", branch, fieldparams.FinalityBranchDepthElectra)
	if err != nil {
		return err
	}
	u.finalityBranch = b
	u.p.FinalityBranch = branch
	return nil
}

func (u *updateElectra) SyncAggregate() *pb.SyncAggregate {
	return u.p.SyncAggregate
}

func (u *updateElectra) SetSyncAggregate(sa *pb.SyncAggregate) {
	u.p.SyncAggregate = sa
}

func (u *updateElectra) SignatureSlot() primitives.Slot {
	return u.p.SignatureSlot
}

func (u *updateElectra) SetSignatureSlot(slot primitives.Slot) {
	u.p.SignatureSlot = slot
}
