package rpc

import (
	"bytes"
	"context"
	"net/http"

	"source.quilibrium.com/quilibrium/monorepo/node/config"

	"github.com/libp2p/go-libp2p/core/peer"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/multiformats/go-multiaddr"
	mn "github.com/multiformats/go-multiaddr/net"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/reflection"
	"source.quilibrium.com/quilibrium/monorepo/node/consensus/master"
	"source.quilibrium.com/quilibrium/monorepo/node/execution"
	"source.quilibrium.com/quilibrium/monorepo/node/keys"
	"source.quilibrium.com/quilibrium/monorepo/node/p2p"
	"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
	"source.quilibrium.com/quilibrium/monorepo/node/store"
)

type RPCServer struct {
	protobufs.UnimplementedNodeServiceServer
	listenAddrGRPC   string
	listenAddrHTTP   string
	logger           *zap.Logger
	dataProofStore   store.DataProofStore
	clockStore       store.ClockStore
	keyManager       keys.KeyManager
	pubSub           p2p.PubSub
	masterClock      *master.MasterClockConsensusEngine
	executionEngines []execution.ExecutionEngine
}

// GetFrameInfo implements protobufs.NodeServiceServer.
func (r *RPCServer) GetFrameInfo(
	ctx context.Context,
	req *protobufs.GetFrameInfoRequest,
) (*protobufs.FrameInfoResponse, error) {
	if bytes.Equal(req.Filter, p2p.BITMASK_ALL) {
		frame, err := r.clockStore.GetMasterClockFrame(
			req.Filter,
			req.FrameNumber,
		)
		if err != nil {
			return nil, errors.Wrap(err, "get frame info")
		}

		return &protobufs.FrameInfoResponse{
			ClockFrame: frame,
		}, nil
	} else if req.Selector == nil {
		frame, _, err := r.clockStore.GetDataClockFrame(
			req.Filter,
			req.FrameNumber,
			false,
		)
		if err != nil {
			return nil, errors.Wrap(err, "get frame info")
		}

		return &protobufs.FrameInfoResponse{
			ClockFrame: frame,
		}, nil
	} else {
		return nil, errors.Wrap(errors.New("not found"), "get frame info")
	}
}

// GetFrames implements protobufs.NodeServiceServer.
func (r *RPCServer) GetFrames(
	ctx context.Context,
	req *protobufs.GetFramesRequest,
) (*protobufs.FramesResponse, error) {
	if bytes.Equal(req.Filter, p2p.BITMASK_ALL) {
		iter, err := r.clockStore.RangeMasterClockFrames(
			req.Filter,
			req.FromFrameNumber,
			req.ToFrameNumber,
		)
		if err != nil {
			return nil, errors.Wrap(err, "get frames")
		}

		frames := []*protobufs.ClockFrame{}
		for iter.First(); iter.Valid(); iter.Next() {
			frame, err := iter.Value()
			if err != nil {
				iter.Close()
				return nil, errors.Wrap(err, "get frames")
			}
			frames = append(frames, frame)
		}

		if err := iter.Close(); err != nil {
			return nil, errors.Wrap(err, "get frames")
		}

		return &protobufs.FramesResponse{
			TruncatedClockFrames: frames,
		}, nil
	} else {
		iter, err := r.clockStore.RangeDataClockFrames(
			req.Filter,
			req.FromFrameNumber,
			req.ToFrameNumber,
		)
		if err != nil {
			return nil, errors.Wrap(err, "get frame info")
		}

		frames := []*protobufs.ClockFrame{}
		for iter.First(); iter.Valid(); iter.Next() {
			frame, err := iter.TruncatedValue()
			if err != nil {
				iter.Close()
				return nil, errors.Wrap(err, "get frames")
			}
			frames = append(frames, frame)
		}

		if err := iter.Close(); err != nil {
			return nil, errors.Wrap(err, "get frames")
		}

		return &protobufs.FramesResponse{
			TruncatedClockFrames: frames,
		}, nil
	}
}

// GetNetworkInfo implements protobufs.NodeServiceServer.
func (r *RPCServer) GetNetworkInfo(
	ctx context.Context,
	req *protobufs.GetNetworkInfoRequest,
) (*protobufs.NetworkInfoResponse, error) {
	return r.pubSub.GetNetworkInfo(), nil
}

// GetNodeInfo implements protobufs.NodeServiceServer.
func (r *RPCServer) GetNodeInfo(
	ctx context.Context,
	req *protobufs.GetNodeInfoRequest,
) (*protobufs.NodeInfoResponse, error) {
	peerID, err := peer.IDFromBytes(r.pubSub.GetPeerID())
	if err != nil {
		return nil, errors.Wrap(err, "getting id from bytes")
	}
	peerScore := r.pubSub.GetPeerScore(r.pubSub.GetPeerID())

	return &protobufs.NodeInfoResponse{
		PeerId:    peerID.String(),
		MaxFrame:  r.masterClock.GetFrame().GetFrameNumber(),
		PeerScore: uint64(peerScore),
		Version: append(
			append([]byte{}, config.GetVersion()...), config.GetPatchNumber(),
		),
	}, nil
}

// GetPeerInfo implements protobufs.NodeServiceServer.
func (r *RPCServer) GetPeerInfo(
	ctx context.Context,
	req *protobufs.GetPeerInfoRequest,
) (*protobufs.PeerInfoResponse, error) {
	resp := &protobufs.PeerInfoResponse{}
	manifests := r.masterClock.GetPeerManifests()
	for _, m := range manifests.PeerManifests {
		multiaddr := r.pubSub.GetMultiaddrOfPeer(m.PeerId)
		addrs := []string{}
		if multiaddr != "" {
			addrs = append(addrs, multiaddr)
		}

		resp.PeerInfo = append(resp.PeerInfo, &protobufs.PeerInfo{
			PeerId:     m.PeerId,
			Multiaddrs: addrs,
			MaxFrame:   m.MasterHeadFrame,
			Timestamp:  m.LastSeen,
			// We can get away with this for this release only, we will want to add
			// version info in manifests.
			Version: config.GetVersion(),
		})
	}
	return resp, nil
}

// Only returns the active amounts earned under 1.4.19 until 2.0
func (r *RPCServer) GetTokenInfo(
	ctx context.Context,
	req *protobufs.GetTokenInfoRequest,
) (*protobufs.TokenInfoResponse, error) {
	// provingKey, err := r.keyManager.GetRawKey(
	// 	"default-proving-key",
	// )
	// if err != nil {
	// 	return nil, errors.Wrap(err, "get token info")
	// }

	// peerBytes := r.pubSub.GetPeerID()
	// peerAddr, err := poseidon.HashBytes(peerBytes)
	// if err != nil {
	// 	panic(err)
	// }

	// addr, err := poseidon.HashBytes(provingKey.PublicKey)
	// if err != nil {
	// 	panic(err)
	// }

	// addrBytes := addr.Bytes()
	// addrBytes = append(make([]byte, 32-len(addrBytes)), addrBytes...)

	// peerAddrBytes := peerAddr.Bytes()
	// peerAddrBytes = append(make([]byte, 32-len(peerAddrBytes)), peerAddrBytes...)

	// frame, err := r.clockStore.GetLatestDataClockFrame(
	// 	append(
	// 		p2p.GetBloomFilter(application.CEREMONY_ADDRESS, 256, 3),
	// 		p2p.GetBloomFilterIndices(application.CEREMONY_ADDRESS, 65536, 24)...,
	// 	),
	// 	nil,
	// )
	// if err != nil {
	// 	return nil, errors.Wrap(err, "get token info")
	// }

	// confirmed, err := application.MaterializeApplicationFromFrame(frame)
	// if err != nil {
	// 	return nil, errors.Wrap(err, "get token info")
	// }

	// confirmedTotal := new(big.Int)
	// ownedTotal := new(big.Int)
	// if confirmed.RewardTrie.Root == nil ||
	// 	(confirmed.RewardTrie.Root.External == nil &&
	// 		confirmed.RewardTrie.Root.Internal == nil) {
	// 	return &protobufs.TokenInfoResponse{
	// 		ConfirmedTokenSupply:   confirmedTotal.FillBytes(make([]byte, 32)),
	// 		UnconfirmedTokenSupply: confirmedTotal.FillBytes(make([]byte, 32)),
	// 		OwnedTokens:            ownedTotal.FillBytes(make([]byte, 32)),
	// 		UnconfirmedOwnedTokens: ownedTotal.FillBytes(make([]byte, 32)),
	// 	}, nil
	// }

	// limbs := []*tries.RewardInternalNode{}
	// if confirmed.RewardTrie.Root.Internal != nil {
	// 	limbs = append(limbs, confirmed.RewardTrie.Root.Internal)
	// } else {
	// 	confirmedTotal = confirmedTotal.Add(
	// 		confirmedTotal,
	// 		new(big.Int).SetUint64(confirmed.RewardTrie.Root.External.Total),
	// 	)
	// 	if bytes.Equal(
	// 		confirmed.RewardTrie.Root.External.Key,
	// 		addrBytes,
	// 	) {
	// 		ownedTotal = ownedTotal.Add(
	// 			ownedTotal,
	// 			new(big.Int).SetUint64(confirmed.RewardTrie.Root.External.Total),
	// 		)
	// 	}
	// }

	// for len(limbs) != 0 {
	// 	nextLimbs := []*tries.RewardInternalNode{}
	// 	for _, limb := range limbs {
	// 		for _, child := range limb.Child {
	// 			child := child
	// 			if child.Internal != nil {
	// 				nextLimbs = append(nextLimbs, child.Internal)
	// 			} else {
	// 				confirmedTotal = confirmedTotal.Add(
	// 					confirmedTotal,
	// 					new(big.Int).SetUint64(child.External.Total),
	// 				)
	// 				if bytes.Equal(
	// 					child.External.Key,
	// 					addrBytes,
	// 				) {
	// 					ownedTotal = ownedTotal.Add(
	// 						ownedTotal,
	// 						new(big.Int).SetUint64(child.External.Total),
	// 					)
	// 				}
	// 				if bytes.Equal(
	// 					child.External.Key,
	// 					peerAddrBytes,
	// 				) {
	// 					ownedTotal = ownedTotal.Add(
	// 						ownedTotal,
	// 						new(big.Int).SetUint64(child.External.Total),
	// 					)
	// 				}
	// 			}
	// 		}
	// 	}
	// 	limbs = nextLimbs
	// }

	// if err != nil {
	// 	return nil, errors.Wrap(err, "get token info")
	// }

	// 1 QUIL = 0x1DCD65000 units
	// conversionFactor, ok := new(big.Int).SetString("1DCD65000", 16)
	// if !ok {
	// 	return nil, errors.Wrap(err, "get token info")
	// }

	total, err := r.dataProofStore.GetTotalReward(r.pubSub.GetPeerID())
	if err != nil {
		panic(err)
	}

	// confirmedTotal = confirmedTotal.Mul(confirmedTotal, conversionFactor)
	// ownedTotal = ownedTotal.Mul(ownedTotal, conversionFactor)

	return &protobufs.TokenInfoResponse{
		// ConfirmedTokenSupply:   confirmedTotal.FillBytes(make([]byte, 32)),
		// UnconfirmedTokenSupply: confirmedTotal.FillBytes(make([]byte, 32)),
		// OwnedTokens: ownedTotal.FillBytes(make([]byte, 32)),
		UnconfirmedOwnedTokens: total.FillBytes(make([]byte, 32)),
	}, nil
}

func (r *RPCServer) GetPeerManifests(
	ctx context.Context,
	req *protobufs.GetPeerManifestsRequest,
) (*protobufs.PeerManifestsResponse, error) {
	return r.masterClock.GetPeerManifests(), nil
}

func NewRPCServer(
	listenAddrGRPC string,
	listenAddrHTTP string,
	logger *zap.Logger,
	dataProofStore store.DataProofStore,
	clockStore store.ClockStore,
	keyManager keys.KeyManager,
	pubSub p2p.PubSub,
	masterClock *master.MasterClockConsensusEngine,
	executionEngines []execution.ExecutionEngine,
) (*RPCServer, error) {
	return &RPCServer{
		listenAddrGRPC:   listenAddrGRPC,
		listenAddrHTTP:   listenAddrHTTP,
		logger:           logger,
		dataProofStore:   dataProofStore,
		clockStore:       clockStore,
		keyManager:       keyManager,
		pubSub:           pubSub,
		masterClock:      masterClock,
		executionEngines: executionEngines,
	}, nil
}

func (r *RPCServer) Start() error {
	s := grpc.NewServer(
		grpc.MaxRecvMsgSize(600*1024*1024),
		grpc.MaxSendMsgSize(600*1024*1024),
	)
	protobufs.RegisterNodeServiceServer(s, r)
	reflection.Register(s)

	mg, err := multiaddr.NewMultiaddr(r.listenAddrGRPC)
	if err != nil {
		return errors.Wrap(err, "start")
	}

	lis, err := mn.Listen(mg)
	if err != nil {
		return errors.Wrap(err, "start")
	}

	go func() {
		if err := s.Serve(mn.NetListener(lis)); err != nil {
			panic(err)
		}
	}()

	if r.listenAddrHTTP != "" {
		m, err := multiaddr.NewMultiaddr(r.listenAddrHTTP)
		if err != nil {
			return errors.Wrap(err, "start")
		}

		ma, err := mn.ToNetAddr(m)
		if err != nil {
			return errors.Wrap(err, "start")
		}

		mga, err := mn.ToNetAddr(mg)
		if err != nil {
			return errors.Wrap(err, "start")
		}

		go func() {
			mux := runtime.NewServeMux()
			opts := []grpc.DialOption{
				grpc.WithTransportCredentials(insecure.NewCredentials()),
				grpc.WithDefaultCallOptions(
					grpc.MaxCallRecvMsgSize(600*1024*1024),
					grpc.MaxCallSendMsgSize(600*1024*1024),
				),
			}

			if err := protobufs.RegisterNodeServiceHandlerFromEndpoint(
				context.Background(),
				mux,
				mga.String(),
				opts,
			); err != nil {
				panic(err)
			}

			if err := http.ListenAndServe(ma.String(), mux); err != nil {
				panic(err)
			}
		}()
	}

	return nil
}
