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

package main

import (
	"crypto/sha512"
	"flag"
	"fmt"

	"filippo.io/edwards25519"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
	dkg "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/dkg/gennaro"
	v1 "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/sharing/v1"
)

const LIMIT = 4
const THRESHOLD = 2

func main() {
	var threshold int
	var limit int
	var help bool
	flag.IntVar(&threshold, "t", THRESHOLD, "the minimum number of participants to sign")
	flag.IntVar(&threshold, "threshold", THRESHOLD, "the minimum number of participants to sign")
	flag.IntVar(&limit, "n", LIMIT, "the total number of participants")
	flag.IntVar(&limit, "limit", LIMIT, "the total number of participants")
	flag.BoolVar(&help, "h", false, "Print this menu")
	flag.BoolVar(&help, "help", false, "Print this menu")
	flag.Parse()

	if help {
		printHelp()
		return
	}

	fmt.Printf("Threshold is %d\n", threshold)
	fmt.Printf("Total participants is %d\n", limit)

	// DEMO doing Gennaro DKG and that signers can compute a signature
	participants := createDkgParticipants(threshold, limit)

	// DKG Round 1
	rnd1Bcast, rnd1P2p := round1(participants)

	// DKG Round 2
	rnd2Bcast := round2(participants, rnd1Bcast, rnd1P2p)

	// DKG Round 3
	verificationKey, signingShares := round3(participants, rnd2Bcast)

	// Signing common setup for all participants
	msg := []byte("All my bitcoin is stored here")
	curve := v1.Ed25519()
	scheme, _ := v1.NewShamir(threshold, limit, curves.NewField(curve.N))
	shares := make([]*v1.ShamirShare, 0, threshold)
	cnt := 0
	for _, share := range signingShares {
		if cnt == threshold {
			break
		}
		cnt++
		shares = append(shares, share)
	}
	sk, err := scheme.Combine(shares...)
	if err != nil {
		panic(err)
	}

	sk = internal.ReverseScalarBytes(sk)
	var ska [32]byte
	copy(ska[:], sk[:])

	skC, err := edwards25519.NewScalar().SetCanonicalBytes(ska[:])
	if err != nil {
		panic(err)
	}
	vk := edwards25519.NewIdentityPoint().ScalarBaseMult(skC)
	vk2, err := internal.BigInt2Ed25519Point(verificationKey.Y)
	if err != nil {
		panic(err)
	}
	if vk.Equal(vk2) == 0 {
		panic("not equal")
	}

	r, s := sign(skC, vk2.Bytes(), []byte("no nonce"), msg)

	ok := verify(vk2.Bytes(), msg, r, s)

	fmt.Printf("Signature verification - %v\n", ok)
}

func sign(skC *edwards25519.Scalar, pubKey, nonce, msg []byte) (*edwards25519.Point, *edwards25519.Scalar) {
	h := sha512.New()
	_, _ = h.Write(nonce)
	_, _ = h.Write(msg)
	digest := h.Sum(nil)

	digestReduced, err := edwards25519.NewScalar().SetUniformBytes(digest)
	if err != nil {
		panic(err)
	}
	r := edwards25519.NewGeneratorPoint().ScalarBaseMult(digestReduced)

	encodedR := r.Bytes()
	h.Reset()
	_, _ = h.Write(encodedR)
	_, _ = h.Write(pubKey)
	_, _ = h.Write(msg)

	k, err := edwards25519.NewScalar().SetUniformBytes(h.Sum(nil))
	if err != nil {
		panic(err)
	}
	s := edwards25519.NewScalar().MultiplyAdd(k, skC, digestReduced)
	return r, s
}

func verify(pk, msg []byte, r *edwards25519.Point, s *edwards25519.Scalar) bool {
	h := sha512.New()
	_, _ = h.Write(r.Bytes())
	_, _ = h.Write(pk)
	_, _ = h.Write(msg)
	k, err := edwards25519.NewScalar().SetUniformBytes(h.Sum(nil))
	if err != nil {
		panic(err)
	}

	minusA, _ := edwards25519.NewIdentityPoint().SetBytes(pk)
	minusA.Negate(minusA)

	lhs := edwards25519.NewIdentityPoint().VarTimeDoubleScalarBaseMult(k, minusA, s)

	return lhs.Equal(r) == 1
}

func round1(participants map[uint32]*dkg.Participant) (map[uint32]dkg.Round1Bcast, map[uint32]dkg.Round1P2PSend) {
	// DKG Round 1
	rnd1Bcast := make(map[uint32]dkg.Round1Bcast, len(participants))
	rnd1P2p := make(map[uint32]dkg.Round1P2PSend, len(participants))
	for id, p := range participants {
		fmt.Printf("Computing DKG Round 1 for participant %d\n", id)
		bcast, p2psend, err := p.Round1(nil)
		if err != nil {
			panic(err)
		}
		rnd1Bcast[id] = bcast
		rnd1P2p[id] = p2psend
	}

	return rnd1Bcast, rnd1P2p
}

func round2(participants map[uint32]*dkg.Participant,
	rnd1Bcast map[uint32]dkg.Round1Bcast,
	rnd1P2p map[uint32]dkg.Round1P2PSend,
) map[uint32]dkg.Round2Bcast {
	rnd2Bcast := make(map[uint32]dkg.Round2Bcast, len(participants))
	for id := range rnd1Bcast {
		fmt.Printf("Computing DKG Round 2 for participant %d\n", id)

		rnd1P2pForP := make(map[uint32]*dkg.Round1P2PSendPacket)
		for jid := range rnd1P2p {
			if jid == id {
				continue
			}
			rnd1P2pForP[jid] = rnd1P2p[jid][id]
		}
		bcast, err := participants[id].Round2(rnd1Bcast, rnd1P2pForP)
		if err != nil {
			panic(err)
		}
		rnd2Bcast[id] = bcast
	}

	return rnd2Bcast
}

func round3(participants map[uint32]*dkg.Participant, rnd2Bcast map[uint32]dkg.Round2Bcast) (*curves.EcPoint, map[uint32]*v1.ShamirShare) {
	signingShares := make(map[uint32]*v1.ShamirShare, len(participants))
	var verificationKey *curves.EcPoint
	for id := range rnd2Bcast {
		fmt.Printf("Computing DKG Round 3 for participant %d\n", id)
		pk, sk, err := participants[id].Round3(rnd2Bcast)
		verificationKey = pk
		if err != nil {
			panic(err)
		}
		fmt.Printf("DKG completed for participant %d\n", id)
		signingShares[id] = sk
	}
	return verificationKey, signingShares
}

func createDkgParticipants(thresh, limit int) map[uint32]*dkg.Participant {
	curve := v1.Ed25519()
	gx, gy := curve.Hash([]byte("Fair is foul, and foul is fair: Hover through the fog and filthy air."))
	generator := &curves.EcPoint{
		Curve: curve,
		X:     gx,
		Y:     gy,
	}
	participants := make(map[uint32]*dkg.Participant, limit)
	for i := 1; i <= limit; i++ {
		otherIds := make([]uint32, limit-1)
		idx := 0
		for j := 1; j <= limit; j++ {
			if i == j {
				continue
			}
			otherIds[idx] = uint32(j)
			idx++
		}
		p, err := dkg.NewParticipant(uint32(i), uint32(thresh), generator, curves.NewEd25519Scalar(), otherIds...)
		if err != nil {
			panic(err)
		}
		participants[uint32(i)] = p
	}
	return participants
}

func printHelp() {
	fmt.Printf(`
bls INPUT
Simulate a DKG using BLS keys
FLAGS:
  -h, --help						Show this help message and exit
  -n, --limit						The total number of participants
  -t, --treshold					The minimum number of participants needed to sign
`)
}