//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

// Package dkls is an implementation of https://eprint.iacr.org/2018/499.pdf
package v0

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"io"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
)

type (
	// ProtocolIterator a generalized interface for multi-party protocols that follows the iterator pattern.
	ProtocolIterator interface {
		// Next runs the next round of the protocol.
		// Inputs are read from rw.Read(); outputs are written to rw.Write().
		// Returns io.EOF when protocol has completed.
		Next(rw io.ReadWriter) error

		// Result returns the final result, if any, of the completed protocol.
		// Reports an error if the protocol has not yet terminated
		// or if an error was encountered during protocol execution.
		Result() (interface{}, error)

		// SetDebug enables or disables (passing a nil value as input) debugging.
		// At the moment, we only print the final dkls dkg result as json value to this log, but if needed more debugging
		// can be added for various steps of the other protocols.
		SetDebug(log io.Writer)
	}

	// Basic protocol interface implementation that calls the next step func in a pre-defined list
	protoStepper struct {
		steps []func(rw io.ReadWriter) error
		step  uint
		log   io.Writer //nolint:structcheck // This is only used in some of the protocols.
	}

	// DKG protocols produce the following result on successful completion
	DkgResult struct {
		DkgState []byte
		Pubkey   *curves.EcPoint
	}
	// AliceDkg DKLS DKG implementation that satisfies the protocol iterator interface.
	AliceDkg struct {
		protoStepper
		Alice *Alice
	}

	// BobDkg DKLS DKG implementation that satisfies the protocol iterator interface.
	BobDkg struct {
		protoStepper
		Bob *Bob
	}

	// AliceSign DKLS sign implementation that satisfies the protocol iterator interface.
	AliceSign struct {
		protoStepper
		alice *Alice
	}

	// BobSign DKLS sign implementation that satisfies the protocol iterator interface.
	BobSign struct {
		protoStepper
		bob *Bob
	}
)

var (
	// Static type assertions
	_ ProtocolIterator = &AliceDkg{}
	_ ProtocolIterator = &BobDkg{}
	_ ProtocolIterator = &AliceSign{}
	_ ProtocolIterator = &BobSign{}

	errProtocolNotComplete = fmt.Errorf("protocol has not completed")
	errNotInitialized      = fmt.Errorf("object has not been initialized")
	errNoSig               = fmt.Errorf("dkls.Alice does not produce a signature")
)

// Reports true if the step index exceeds the number of steps
func (p *protoStepper) complete() bool { return int(p.step) >= len(p.steps) }

// Next runs the next step in the protocol and reports errors or increments the step index
func (p *protoStepper) Next(rw io.ReadWriter) error {
	// Signal EOF if the protocol has completed.
	if p.complete() {
		return io.EOF
	}

	// Run the current protocol step and report any errors
	if err := p.steps[p.step](rw); err != nil {
		return err
	}

	// Increment the step index and report success
	p.step++
	return nil
}

// NewAliceDkg creates a new protocol that can compute a DKG as Alice
func NewAliceDkg(params *Params) *AliceDkg {
	a := &AliceDkg{Alice: NewAlice(params)}
	a.steps = []func(rw io.ReadWriter) error{
		func(rw io.ReadWriter) error { return a.Alice.dkgCommit(rw) },
		a.Alice.dkgVerify,
		func(rw io.ReadWriter) error {
			if err := a.Alice.Receiver.pubKey(rw); err != nil {
				return err
			}
			return a.Alice.Receiver.padTransfer(rw)
		},
		a.Alice.Receiver.verification,
		func(rw io.ReadWriter) error { return a.Alice.Receiver.final(rw) },
	}
	return a
}

// NewBobDkg Creates a new protocol that can compute a DKG as Bob
func NewBobDkg(params *Params) *BobDkg {
	b := &BobDkg{Bob: NewBob(params)}
	b.steps = []func(rw io.ReadWriter) error{
		b.Bob.dkgCommit,
		func(rw io.ReadWriter) error {
			if err := b.Bob.dkgVerify(rw); err != nil {
				return err
			}
			return b.Bob.Sender.pubKey(rw)
		},
		b.Bob.Sender.padTransfer,
		b.Bob.Sender.verification,
	}
	return b
}

func (a *AliceDkg) SetDebug(log io.Writer) {
	a.log = log
}

// Result Returns an encoded version of Alice as sequence of bytes that can be used to initialize an AliceSign protocol.
func (a *AliceDkg) Result() (interface{}, error) {
	// Sanity check
	if !a.complete() {
		return nil, errProtocolNotComplete
	}
	if a.Alice == nil {
		return nil, errNotInitialized
	}

	// Encode DKG state of alice for future signing
	state, err := EncodeAlice(a.Alice)
	if err != nil {
		return nil, err
	}

	result := &DkgResult{
		DkgState: state,
		Pubkey:   a.Alice.Pk,
	}

	if a.log != nil {
		resultBytes, err := json.Marshal(result)
		if err != nil {
			_, _ = a.log.Write([]byte(fmt.Sprintf("Error while json marshaling the results: %v", err)))
		} else {
			_, _ = a.log.Write([]byte("Alice Dkg result:\n"))
			_, _ = a.log.Write(resultBytes)
		}
	}
	return result, nil
}

func (b *BobDkg) SetDebug(log io.Writer) {
	b.log = log
}

// Result returns an encoded version of Alice as sequence of bytes that can be used to
// initialize an AliceSign protocol.
func (b *BobDkg) Result() (interface{}, error) {
	// Sanity check
	if !b.complete() {
		return nil, errProtocolNotComplete
	}
	if b.Bob == nil {
		return nil, errNotInitialized
	}
	// Encode DKG state of bob for future signing
	state, err := EncodeBob(b.Bob)
	if err != nil {
		return nil, err
	}

	result := &DkgResult{
		DkgState: state,
		Pubkey:   b.Bob.Pk,
	}
	if b.log != nil {
		resultBytes, err := json.Marshal(result)
		if err != nil {
			_, _ = b.log.Write([]byte(fmt.Sprintf("Error while json marshaling the results: %v", err)))
		} else {
			_, _ = b.log.Write([]byte("Alice Dkg result:\n"))
			_, _ = b.log.Write(resultBytes)
		}
	}
	return result, nil
}

// Creates a new protocol that can compute a signature as Alice.
// Requires dkg state that was produced at the end of DKG.Result().
func NewAliceSign(params *Params, msg []byte, dkgResult []byte) (*AliceSign, error) {
	// Reconstitute Alice
	alice, err := DecodeAlice(params, dkgResult)
	if err != nil {
		return nil, err
	}

	// Configure Alice with her 1 signing step
	a := &AliceSign{alice: alice}
	a.steps = []func(rw io.ReadWriter) error{
		func(rw io.ReadWriter) error { return alice.signInit(msg, rw) },
	}
	return a, nil
}

// NewBobSign creates a new protocol that can compute a signature as Bob.
// Requires dkg state that was produced at the end of DKG.Result().
func NewBobSign(params *Params, msg []byte, dkgResult []byte) (*BobSign, error) {
	// Reconstitute Bob
	bob, err := DecodeBob(params, dkgResult)
	if err != nil {
		return nil, err
	}

	// Configure Bob with 2 signing steps
	b := &BobSign{bob: bob}
	b.steps = []func(rw io.ReadWriter) error{
		func(rw io.ReadWriter) error { return bob.signInit(rw) },
		func(rw io.ReadWriter) error { return bob.signFinal(msg, rw) },
	}
	return b, nil
}

func (a *AliceSign) SetDebug(log io.Writer) {
	a.log = log
}

// Result always returns errNoSig.
// Alice does not compute a signature in the DKLS protocol; only Bob computes the signature.
func (a *AliceSign) Result() (interface{}, error) {
	return nil, errNoSig
}

func (b *BobSign) SetDebug(log io.Writer) {
	b.log = log
}

// If the signing protocol completed successfully, returns the signature that
// Bob computed as a *core.EcdsaSignature.
func (d *BobSign) Result() (interface{}, error) {
	// We can't produce a signature until the protocol completes
	if !d.complete() {
		return nil, errProtocolNotComplete
	}
	if d.bob == nil {
		// Object wasn't created with NewXSign()
		return nil, errNotInitialized

	}
	return d.bob.Sig, nil
}

// Encodes an alice object as a byte sequence after DKG has been completed.
func EncodeAlice(a *Alice) ([]byte, error) {
	var buf bytes.Buffer
	err := gob.NewEncoder(&buf).Encode(a)
	return buf.Bytes(), err
}

// Decodes an alice object that was encoded after DKG.
func DecodeAlice(params *Params, b []byte) (*Alice, error) {
	alice := NewAlice(params)
	err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(alice)
	return alice, err
}

// Encodes a bob object as a byte sequence after DKG has been completed.
func EncodeBob(b *Bob) ([]byte, error) {
	var buf bytes.Buffer
	err := gob.NewEncoder(&buf).Encode(b)
	return buf.Bytes(), err
}

// Decodes an bob object that was encoded after DKG.
func DecodeBob(params *Params, b []byte) (*Bob, error) {
	bob := NewBob(params)
	err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(bob)
	return bob, err
}
