package application

import (
	"bytes"
	"fmt"

	"github.com/pkg/errors"
	"golang.org/x/sync/errgroup"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
	"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)

func (a *CeremonyApplication) applyTranscriptShare(
	share *protobufs.CeremonyTranscriptShare,
) error {
	if len(share.AdditiveG1Powers) != len(a.LatestTranscript.G1Powers)-1 {
		return errors.Wrap(errors.New("invalid g1s"), "apply transcript share")
	}
	if len(share.AdditiveG2Powers) != len(a.LatestTranscript.G2Powers)-1 {
		return errors.Wrap(errors.New("invalid g2s"), "apply transcript share")
	}
	if share.AdditiveG1_256Witness == nil ||
		share.AdditiveG1_256Witness.KeyValue == nil {
		return errors.Wrap(
			errors.New("invalid g1 witness"),
			"apply transcript share",
		)
	}
	if _, err := curves.BLS48581G1().Point.FromAffineCompressed(
		share.AdditiveG1_256Witness.KeyValue,
	); err != nil {
		return errors.Wrap(
			errors.Wrap(err, "invalid g1 witness"),
			"apply transcript share",
		)
	}
	if share.AdditiveG2_256Witness == nil ||
		share.AdditiveG2_256Witness.KeyValue == nil {
		return errors.Wrap(
			errors.New("invalid g2 witness"),
			"apply transcript share",
		)
	}

	for _, s := range a.TranscriptShares {
		if bytes.Equal(
			s.AdditiveG1Powers[0].KeyValue,
			share.AdditiveG1Powers[0].KeyValue,
		) {
			return nil
		}
	}

	matchFound := false

	for _, c := range a.FinalCommits {
		if bytes.Equal(
			share.ProverSignature.PublicKey.KeyValue,
			c.ProverSignature.PublicKey.KeyValue,
		) {
			matchFound = true
			break
		}
	}

	if !matchFound {
		return errors.Wrap(
			errors.New(
				fmt.Sprintf(
					"no corresponding commit in commit set (size %d)",
					len(a.FinalCommits),
				),
			),
			"apply transcript share",
		)
	}

	if err := share.VerifySignature(); err != nil {
		return errors.Wrap(err, "apply transcript share")
	}

	eg := errgroup.Group{}
	eg.SetLimit(100)

	for i, g1 := range a.LatestTranscript.G1Powers {
		i := i
		g1 := g1
		eg.Go(func() error {
			if _, err := curves.BLS48581G1().Point.FromAffineCompressed(
				g1.KeyValue,
			); err != nil {
				return errors.Wrap(
					errors.Wrap(err, fmt.Sprintf("invalid g1 at position %d", i)),
					"apply transcript share",
				)
			}
			return nil
		})
	}

	for i, g2 := range a.LatestTranscript.G2Powers {
		i := i
		g2 := g2
		eg.Go(func() error {
			if _, err := curves.BLS48581G2().Point.FromAffineCompressed(
				g2.KeyValue,
			); err != nil {
				return errors.Wrap(
					errors.Wrap(err, fmt.Sprintf("invalid g2 at position %d", i)),
					"apply transcript share",
				)
			}
			return nil
		})
	}

	if err := eg.Wait(); err != nil {
		return err
	}

	exists := false
	for _, s := range a.TranscriptShares {
		exists = bytes.Equal(
			s.ProverSignature.Signature,
			share.ProverSignature.Signature,
		)
		if exists {
			break
		}
	}

	if !exists {
		a.TranscriptShares = append(a.TranscriptShares, share)
	}

	return nil
}

func (a *CeremonyApplication) finalizeTranscript() error {
	a.UpdatedTranscript = &protobufs.CeremonyTranscript{
		G1Powers: make(
			[]*protobufs.BLS48581G1PublicKey,
			len(a.LatestTranscript.G1Powers),
		),
		G2Powers: make(
			[]*protobufs.BLS48581G2PublicKey,
			len(a.LatestTranscript.G2Powers),
		),
		RunningG1_256Witnesses: a.LatestTranscript.RunningG1_256Witnesses,
		RunningG2_256Powers:    a.LatestTranscript.RunningG2_256Powers,
	}

	a.UpdatedTranscript.G1Powers[0] = a.LatestTranscript.G1Powers[0]
	a.UpdatedTranscript.G2Powers[0] = a.LatestTranscript.G2Powers[0]

	for i := range a.UpdatedTranscript.G1Powers[1:] {
		g1, err := curves.BLS48581G1().Point.FromAffineCompressed(
			a.TranscriptShares[0].AdditiveG1Powers[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "finalize transcript")
		}

		if len(a.TranscriptShares) > 1 {
			for _, share := range a.TranscriptShares[1:] {
				ag1, err := curves.BLS48581G1().Point.FromAffineCompressed(
					share.AdditiveG1Powers[i].KeyValue,
				)
				if err != nil {
					return errors.Wrap(err, "finalize transcript")
				}

				g1 = g1.Add(ag1)
			}
		}

		if !g1.IsOnCurve() || g1.IsIdentity() {
			return errors.Wrap(
				errors.New("invalid g1 power"),
				"finalize transcript",
			)
		}

		a.UpdatedTranscript.G1Powers[i+1] = &protobufs.BLS48581G1PublicKey{
			KeyValue: g1.ToAffineCompressed(),
		}
	}

	for i := range a.UpdatedTranscript.G2Powers[1:] {
		g2, err := curves.BLS48581G2().Point.FromAffineCompressed(
			a.TranscriptShares[0].AdditiveG2Powers[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "finalize transcript")
		}

		if len(a.TranscriptShares) > 1 {
			for _, share := range a.TranscriptShares[1:] {
				ag2, err := curves.BLS48581G2().Point.FromAffineCompressed(
					share.AdditiveG2Powers[i].KeyValue,
				)
				if err != nil {
					return errors.Wrap(err, "finalize transcript")
				}

				g2 = g2.Add(ag2)
			}
		}

		if !g2.IsOnCurve() || g2.IsIdentity() {
			return errors.Wrap(
				errors.New("invalid g2 power"),
				"finalize transcript",
			)
		}

		a.UpdatedTranscript.G2Powers[i+1] = &protobufs.BLS48581G2PublicKey{
			KeyValue: g2.ToAffineCompressed(),
		}
	}

	g1Witness, err := curves.BLS48581G1().Point.FromAffineCompressed(
		a.TranscriptShares[0].AdditiveG1_256Witness.KeyValue,
	)
	if err != nil {
		return errors.Wrap(err, "finalize transcript")
	}

	if len(a.TranscriptShares) > 1 {
		for _, share := range a.TranscriptShares[1:] {
			ag1, err := curves.BLS48581G1().Point.FromAffineCompressed(
				share.AdditiveG1_256Witness.KeyValue,
			)
			if err != nil {
				return errors.Wrap(err, "finalize transcript")
			}

			g1Witness = g1Witness.Add(ag1)
		}
	}

	if !g1Witness.IsOnCurve() || g1Witness.IsIdentity() {
		return errors.Wrap(
			errors.New("invalid witness"),
			"finalize transcript",
		)
	}

	a.UpdatedTranscript.RunningG1_256Witnesses = append(
		a.UpdatedTranscript.RunningG1_256Witnesses,
		&protobufs.BLS48581G1PublicKey{
			KeyValue: g1Witness.ToAffineCompressed(),
		},
	)

	a.UpdatedTranscript.RunningG2_256Powers = append(
		a.UpdatedTranscript.RunningG2_256Powers,
		a.UpdatedTranscript.G2Powers[len(a.UpdatedTranscript.G2Powers)-1],
	)

	return nil
}