package test

import (
	"bytes"
	"context"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightninglabs/lndclient"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/keychain"
	"github.com/lightningnetwork/lnd/lnrpc/signrpc"
)

type mockSigner struct {
	lndclient.SignerClient

	lnd *LndMockServices
}

var _ lndclient.SignerClient = (*mockSigner)(nil)

func (s *mockSigner) RawClientWithMacAuth(
	ctx context.Context) (context.Context, time.Duration,
	signrpc.SignerClient) {

	return ctx, 0, nil
}

func (s *mockSigner) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
	signDescriptors []*lndclient.SignDescriptor,
	_ []*wire.TxOut) ([][]byte, error) {

	s.lnd.SignOutputRawChannel <- SignOutputRawRequest{
		Tx:              tx,
		SignDescriptors: signDescriptors,
	}

	rawSigs := make([][]byte, len(signDescriptors))
	for i := range signDescriptors {
		sig := make([]byte, 64)
		sig[0] = byte(i + 1)
		rawSigs[i] = sig
	}

	return rawSigs, nil
}

func (s *mockSigner) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
	signDescriptors []*lndclient.SignDescriptor,
	prevOutputs []*wire.TxOut) ([]*input.Script, error) {

	return nil, fmt.Errorf("unimplemented")
}

func (s *mockSigner) SignMessage(ctx context.Context, msg []byte,
	locator keychain.KeyLocator, opts ...lndclient.SignMessageOption) (
	[]byte, error) {

	return s.lnd.Signature, nil
}

func (s *mockSigner) VerifyMessage(ctx context.Context, msg, sig []byte,
	pubkey [33]byte, opts ...lndclient.VerifyMessageOption) (bool, error) {

	// Make the mock somewhat functional by asserting that the message and
	// signature is what we expect from the mock parameters.
	mockAssertion := bytes.Equal(msg, []byte(s.lnd.SignatureMsg)) &&
		bytes.Equal(sig, s.lnd.Signature)

	return mockAssertion, nil
}

func (s *mockSigner) DeriveSharedKey(context.Context, *btcec.PublicKey,
	*keychain.KeyLocator) ([32]byte, error) {

	return [32]byte{4, 5, 6}, nil
}

// MuSig2CreateSession creates a new MuSig2 signing session using the local
// key identified by the key locator. The complete list of all public keys of
// all signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
func (s *mockSigner) MuSig2CreateSession(context.Context, input.MuSig2Version,
	*keychain.KeyLocator, [][]byte, ...lndclient.MuSig2SessionOpts) (
	*input.MuSig2SessionInfo, error) {

	const testPubKey = "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
	pubKeyBytes, err := hex.DecodeString(testPubKey)
	if err != nil {
		return nil, err
	}

	combinedKey, err := schnorr.ParsePubKey(pubKeyBytes)
	if err != nil {
		return nil, err
	}

	return &input.MuSig2SessionInfo{
		CombinedKey:   combinedKey,
		HaveAllNonces: true,
	}, nil
}

// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (s *mockSigner) MuSig2RegisterNonces(context.Context, [32]byte,
	[][66]byte) (bool, error) {

	return true, nil
}

// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (s *mockSigner) MuSig2Sign(context.Context, [32]byte, [32]byte,
	bool) ([]byte, error) {

	return nil, nil
}

// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (s *mockSigner) MuSig2CombineSig(context.Context, [32]byte,
	[][]byte) (bool, []byte, error) {

	sig := make([]byte, 64)
	sig[0] = 42

	return true, sig, nil
}

// MuSig2Cleanup removes a session from memory to free up resources.
func (s *mockSigner) MuSig2Cleanup(context.Context, [32]byte) error {
	return nil
}
