/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package discovery

import (
	"context"

	"github.com/hyperledger/fabric-protos-go/discovery"
	"github.com/hyperledger/fabric/cmd/common"
	"github.com/hyperledger/fabric/cmd/common/comm"
	"github.com/hyperledger/fabric/cmd/common/signer"
	discoveryclient "github.com/hyperledger/fabric/discovery/client"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/pkg/errors"
)

//go:generate mockery -dir . -name LocalResponse -case underscore -output mocks/

// LocalResponse is the local interface used to generate mocks for foreign interface.
type LocalResponse interface {
	discoveryclient.LocalResponse
}

//go:generate mockery -dir . -name ChannelResponse -case underscore -output mocks/

// ChannelResponse is the local interface used to generate mocks for foreign interface.
type ChannelResponse interface {
	discoveryclient.ChannelResponse
}

//go:generate mockery -dir . -name ServiceResponse -case underscore -output mocks/

// ServiceResponse represents a response sent from the discovery service
type ServiceResponse interface {
	// ForChannel returns a ChannelResponse in the context of a given channel
	ForChannel(string) discoveryclient.ChannelResponse

	// ForLocal returns a LocalResponse in the context of no channel
	ForLocal() discoveryclient.LocalResponse

	// Raw returns the raw response from the server
	Raw() *discovery.Response
}

type response struct {
	raw *discovery.Response
	discoveryclient.Response
}

func (r *response) Raw() *discovery.Response {
	return r.raw
}

// ClientStub is a stub that communicates with the discovery service
// using the discovery client implementation
type ClientStub struct {
}

// Send sends the request, and receives a response
func (stub *ClientStub) Send(server string, conf common.Config, req *discoveryclient.Request) (ServiceResponse, error) {
	comm, err := comm.NewClient(conf.TLSConfig)
	if err != nil {
		return nil, err
	}
	signer, err := signer.NewSigner(conf.SignerConfig)
	if err != nil {
		return nil, err
	}
	timeout, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	defer cancel()

	disc := discoveryclient.NewClient(comm.NewDialer(server), signer.Sign, 0)

	resp, err := disc.Send(timeout, req, &discovery.AuthInfo{
		ClientIdentity:    signer.Creator,
		ClientTlsCertHash: comm.TLSCertHash,
	})
	if err != nil {
		return nil, errors.Errorf("failed connecting to %s: %v", server, err)
	}
	return &response{
		Response: resp,
	}, nil
}

// RawStub is a stub that communicates with the discovery service
// without any intermediary.
type RawStub struct {
}

// Send sends the request, and receives a response
func (stub *RawStub) Send(server string, conf common.Config, req *discoveryclient.Request) (ServiceResponse, error) {
	comm, err := comm.NewClient(conf.TLSConfig)
	if err != nil {
		return nil, err
	}
	signer, err := signer.NewSigner(conf.SignerConfig)
	if err != nil {
		return nil, err
	}
	timeout, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	defer cancel()

	req.Authentication = &discovery.AuthInfo{
		ClientIdentity:    signer.Creator,
		ClientTlsCertHash: comm.TLSCertHash,
	}

	payload := protoutil.MarshalOrPanic(req.Request)
	sig, err := signer.Sign(payload)
	if err != nil {
		return nil, err
	}

	cc, err := comm.NewDialer(server)()
	if err != nil {
		return nil, err
	}
	resp, err := discovery.NewDiscoveryClient(cc).Discover(timeout, &discovery.SignedRequest{
		Payload:   payload,
		Signature: sig,
	})

	if err != nil {
		return nil, err
	}

	return &response{
		raw: resp,
	}, nil
}
