package application

import (
	"bytes"
	"crypto/rand"

	"github.com/pkg/errors"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
	"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)

func (a *CeremonyApplication) applyTranscript(
	transcript *protobufs.CeremonyTranscript,
) error {
	if len(a.UpdatedTranscript.G1Powers) != len(transcript.G1Powers) {
		return errors.Wrap(errors.New("invalid g1s"), "apply transcript")
	}
	if len(a.UpdatedTranscript.G2Powers) != len(transcript.G2Powers) {
		return errors.Wrap(errors.New("invalid g2s"), "apply transcript")
	}
	if len(a.UpdatedTranscript.RunningG1_256Witnesses) !=
		len(transcript.RunningG1_256Witnesses) ||
		len(transcript.RunningG1_256Witnesses) !=
			len(a.LatestTranscript.RunningG1_256Witnesses)+1 {
		return errors.Wrap(
			errors.New("invalid witnesses"),
			"apply transcript",
		)
	}
	if len(a.UpdatedTranscript.RunningG2_256Powers) !=
		len(transcript.RunningG2_256Powers) ||
		len(transcript.RunningG2_256Powers) !=
			len(a.LatestTranscript.RunningG2_256Powers)+1 {
		return errors.Wrap(
			errors.New("invalid g2^256 powers"),
			"apply transcript",
		)
	}

	g1s := make([]curves.Point, len(a.UpdatedTranscript.G1Powers))

	for i := range a.UpdatedTranscript.G1Powers {
		i := i
		if !bytes.Equal(
			a.UpdatedTranscript.G1Powers[i].KeyValue,
			transcript.G1Powers[i].KeyValue,
		) {
			return errors.Wrap(errors.New("invalid g1s"), "apply transcript")
		}

		g1 := &curves.PointBls48581G1{}
		x, err := g1.FromAffineCompressed(
			a.UpdatedTranscript.G1Powers[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "apply transcript")
		}

		g1s[i] = x
	}

	g2s := make([]curves.Point, len(a.UpdatedTranscript.G2Powers))
	for i := range a.UpdatedTranscript.G2Powers {
		i := i
		if !bytes.Equal(
			a.UpdatedTranscript.G2Powers[i].KeyValue,
			transcript.G2Powers[i].KeyValue,
		) {
			return errors.Wrap(errors.New("invalid g2s"), "apply transcript")
		}

		g2 := &curves.PointBls48581G2{}
		x, err := g2.FromAffineCompressed(
			a.UpdatedTranscript.G2Powers[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "apply transcript")
		}

		g2s[i] = x
	}

	g1Witnesses := []*curves.PointBls48581G1{}
	for i := range a.UpdatedTranscript.RunningG1_256Witnesses {
		if !bytes.Equal(
			a.UpdatedTranscript.RunningG1_256Witnesses[i].KeyValue,
			transcript.RunningG1_256Witnesses[i].KeyValue,
		) {
			return errors.Wrap(errors.New("invalid g1 witnesses"), "apply transcript")
		}

		g1w := &curves.PointBls48581G1{}
		w, err := g1w.FromAffineCompressed(
			a.UpdatedTranscript.RunningG1_256Witnesses[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "apply transcript")
		}
		g1w, _ = w.(*curves.PointBls48581G1)

		g1Witnesses = append(g1Witnesses, g1w)
	}

	g2Powers := []*curves.PointBls48581G2{}
	for i := range a.UpdatedTranscript.RunningG2_256Powers {
		if !bytes.Equal(
			a.UpdatedTranscript.RunningG2_256Powers[i].KeyValue,
			transcript.RunningG2_256Powers[i].KeyValue,
		) {
			return errors.Wrap(
				errors.New("invalid g2^256 powers"),
				"apply transcript",
			)
		}

		g2w := &curves.PointBls48581G2{}
		w, err := g2w.FromAffineCompressed(
			a.UpdatedTranscript.RunningG2_256Powers[i].KeyValue,
		)
		if err != nil {
			return errors.Wrap(err, "apply transcript")
		}
		g2w, _ = w.(*curves.PointBls48581G2)

		g2Powers = append(g2Powers, g2w)
	}

	if !g2Powers[len(g2Powers)-1].Equal(g2s[len(g2s)-1]) {
		return errors.Wrap(
			errors.New("invalid running g2^256 power"),
			"apply transcript",
		)
	}

	for i := 0; i < len(a.LatestTranscript.RunningG1_256Witnesses); i++ {
		if !bytes.Equal(
			a.LatestTranscript.RunningG1_256Witnesses[i].KeyValue,
			a.UpdatedTranscript.RunningG1_256Witnesses[i].KeyValue,
		) {
			return errors.Wrap(
				errors.New("running witness mismatch"),
				"apply transcript",
			)
		}
	}

	for i := 0; i < len(a.LatestTranscript.RunningG2_256Powers); i++ {
		if !bytes.Equal(
			a.LatestTranscript.RunningG2_256Powers[i].KeyValue,
			a.UpdatedTranscript.RunningG2_256Powers[i].KeyValue,
		) {
			return errors.Wrap(
				errors.New("running g2^256 power mismatch"),
				"apply transcript",
			)
		}
	}

	mpg2 := curves.BLS48581G2().Point.Generator().(curves.PairingPoint)
	mpg2n := g2s[1].Neg().(curves.PairingPoint)

	mpg1 := curves.BLS48581G1().Point.Generator().(curves.PairingPoint)
	mpg1n := g1s[1].Neg().(curves.PairingPoint)

	randoms := []curves.Scalar{}
	sum := curves.BLS48581G1().Scalar.Zero()

	for i := 0; i < len(g1s)-1; i++ {
		randoms = append(randoms, curves.BLS48581G1().Scalar.Random(rand.Reader))
		sum = sum.Add(randoms[i])
	}

	g1CheckR := g1s[0].SumOfProducts(g1s[1:], randoms)
	g1CheckL := g1s[0].SumOfProducts(g1s[:len(g1s)-1], randoms)

	if !mpg2.MultiPairing(
		g1CheckL.(curves.PairingPoint),
		mpg2n.Mul(sum).(curves.PairingPoint),
		g1CheckR.(curves.PairingPoint),
		mpg2.Mul(sum).(curves.PairingPoint),
	).IsOne() {
		return errors.Wrap(
			errors.New("pairing check failed for g1s"),
			"apply transcript",
		)
	}

	var g2CheckL, g2CheckR curves.Point
	g2Sum := curves.BLS48581G1().Scalar.Zero()
	for i := 0; i < len(g2s)-1; i++ {
		g2Sum = g2Sum.Add(randoms[i])
		if g2CheckL == nil {
			g2CheckL = g2s[0].Mul(randoms[0])
			g2CheckR = g2s[1].Mul(randoms[0])
		} else {
			g2CheckL = g2CheckL.Add(g2s[i].Mul(randoms[i]))
			g2CheckR = g2CheckR.Add(g2s[i+1].Mul(randoms[i]))
		}
	}

	if !mpg2.MultiPairing(
		mpg1n.Mul(g2Sum).(curves.PairingPoint),
		g2CheckL.(curves.PairingPoint),
		mpg1.Mul(g2Sum).(curves.PairingPoint),
		g2CheckR.(curves.PairingPoint),
	).IsOne() {
		return errors.Wrap(
			errors.New("pairing check failed for g2s"),
			"apply transcript",
		)
	}

	mp3 := make([]curves.PairingPoint, (len(g2Powers)-1)*4)
	for i := 0; i < len(g2Powers)-1; i++ {
		i := i
		mp3[i*4+0] = g1Witnesses[i+1].Neg().(curves.PairingPoint)
		mp3[i*4+1] = g2Powers[i]
		mp3[i*4+2] = mpg1
		mp3[i*4+3] = g2Powers[i+1]
	}

	l := mp3[0].MultiPairing(mp3...)
	if !l.IsOne() {
		return errors.Wrap(
			errors.New("pairing check failed for witnesses"),
			"apply transcript",
		)
	}

	a.LatestTranscript = a.UpdatedTranscript
	a.UpdatedTranscript = nil

	return nil
}
