package application

import (
	"bytes"
	"encoding/binary"

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

func (a *CeremonyApplication) applySeenProverAttestation(
	seenProverAttestation *protobufs.CeremonySeenProverAttestation,
) error {
	if seenProverAttestation.SeenProverKey == nil ||
		seenProverAttestation.SeenProverKey.KeyValue == nil {
		return errors.Wrap(
			errors.New("signature is nil"),
			"apply seen prover attestation",
		)
	}

	inParticipantList := false
	for _, p := range a.ActiveParticipants {
		if bytes.Equal(
			p.PublicKeySignatureEd448.PublicKey.KeyValue,
			seenProverAttestation.SeenProverKey.KeyValue,
		) {
			inParticipantList = true
			break
		}
	}

	if !inParticipantList {
		return errors.Wrap(
			errors.New("prover not in active participant list"),
			"apply seen prover attestation",
		)
	}

	b := binary.BigEndian.AppendUint64(
		[]byte("lastseen"),
		seenProverAttestation.LastSeenFrame,
	)
	b = append(b, seenProverAttestation.SeenProverKey.KeyValue...)
	signature := seenProverAttestation.GetProverSignature()
	if signature == nil {
		return errors.Wrap(
			errors.New("signature is nil"),
			"apply seen prover attestation",
		)
	}

	if err := signature.Verify(b); err != nil {
		return errors.Wrap(err, "apply seen prover attestation")
	}

	replaced := false
	for i, att := range a.LatestSeenProverAttestations {
		att := att
		if bytes.Equal(
			att.SeenProverKey.KeyValue,
			seenProverAttestation.SeenProverKey.KeyValue,
		) &&
			bytes.Equal(
				att.ProverSignature.PublicKey.KeyValue,
				seenProverAttestation.ProverSignature.PublicKey.KeyValue,
			) && att.LastSeenFrame < seenProverAttestation.LastSeenFrame {
			a.LatestSeenProverAttestations[i] = att
			replaced = true
			break
		}
	}

	if !replaced {
		a.LatestSeenProverAttestations = append(
			a.LatestSeenProverAttestations,
			seenProverAttestation,
		)
	}

	return nil
}

func (a *CeremonyApplication) applyDroppedProverAttestation(
	droppedProverAttestation *protobufs.CeremonyDroppedProverAttestation,
) error {
	if droppedProverAttestation.DroppedProverKey == nil ||
		droppedProverAttestation.DroppedProverKey.KeyValue == nil {
		return errors.Wrap(
			errors.New("signature is nil"),
			"apply dropped prover attestation",
		)
	}

	inParticipantList := false
	for _, p := range a.ActiveParticipants {
		if bytes.Equal(
			p.PublicKeySignatureEd448.PublicKey.KeyValue,
			droppedProverAttestation.DroppedProverKey.KeyValue,
		) {
			inParticipantList = true
			break
		}
	}

	if !inParticipantList {
		return errors.Wrap(
			errors.New("prover not in active participant list"),
			"apply dropped prover attestation",
		)
	}

	b := binary.BigEndian.AppendUint64(
		[]byte("dropped"),
		droppedProverAttestation.LastSeenFrame,
	)
	b = append(b, droppedProverAttestation.DroppedProverKey.KeyValue...)
	signature := droppedProverAttestation.GetProverSignature()
	if signature == nil {
		return errors.Wrap(
			errors.New("signature is nil"),
			"apply dropped prover attestation",
		)
	}

	if err := signature.Verify(b); err != nil {
		return errors.Wrap(err, "apply dropped prover attestation")
	}

	replaced := false
	for i, att := range a.DroppedParticipantAttestations {
		att := att
		if bytes.Equal(
			att.DroppedProverKey.KeyValue,
			droppedProverAttestation.DroppedProverKey.KeyValue,
		) &&
			bytes.Equal(
				att.ProverSignature.PublicKey.KeyValue,
				droppedProverAttestation.ProverSignature.PublicKey.KeyValue,
			) && att.LastSeenFrame < droppedProverAttestation.LastSeenFrame {
			a.DroppedParticipantAttestations[i] = att
			replaced = true
			break
		}
	}

	if !replaced {
		a.DroppedParticipantAttestations = append(
			a.DroppedParticipantAttestations,
			droppedProverAttestation,
		)
	}

	return nil
}

func (a *CeremonyApplication) applyTranscriptCommit(
	transcriptCommit *protobufs.CeremonyTranscriptCommit,
) error {
	if transcriptCommit.ContributionSignature == nil ||
		transcriptCommit.ProverSignature == nil ||
		transcriptCommit.ContributionSignature.PublicKey == nil ||
		transcriptCommit.ProverSignature.PublicKey == nil {
		return errors.Wrap(
			errors.New("signature is nil"),
			"apply transcript commit",
		)
	}

	point, err := curves.BLS48581G2().Point.FromAffineCompressed(
		transcriptCommit.ContributionSignature.PublicKey.KeyValue,
	)
	if err != nil {
		return errors.Wrap(err, "apply transcript commit")
	}

	if err := VerifySignatureOfProverKey(
		transcriptCommit.ProverSignature.PublicKey.KeyValue,
		transcriptCommit.ContributionSignature.Signature,
		point,
	); err != nil {
		return errors.Wrap(err, "apply transcript commit")
	}

	if err := transcriptCommit.ProverSignature.Verify(
		transcriptCommit.ContributionSignature.PublicKey.KeyValue,
	); err != nil {
		return errors.Wrap(err, "apply transcript commit")
	}

	inParticipantList := false
	for _, p := range a.ActiveParticipants {
		if bytes.Equal(
			p.PublicKeySignatureEd448.PublicKey.KeyValue,
			transcriptCommit.ProverSignature.PublicKey.KeyValue,
		) {
			inParticipantList = true
			break
		}
	}

	if !inParticipantList {
		return errors.Wrap(
			errors.New("prover not in active participant list"),
			"apply transcript commit",
		)
	}

	maxRounds := uint64(1)
	for i := 0; i < len(a.ActiveParticipants)-1; i++ {
		maxRounds = maxRounds << 1
	}

	if len(a.TranscriptRoundAdvanceCommits) == 0 {
		a.TranscriptRoundAdvanceCommits = []*protobufs.CeremonyAdvanceRound{
			{
				Commits: []*protobufs.CeremonyTranscriptCommit{},
			},
		}
	}

	if len(a.TranscriptRoundAdvanceCommits[a.RoundCount-1].Commits) ==
		len(a.ActiveParticipants) {
		if maxRounds ==
			uint64(len(a.TranscriptRoundAdvanceCommits)) {
			return errors.Wrap(
				errors.New("round limit exceeded"),
				"apply transcript commit",
			)
		}
		a.TranscriptRoundAdvanceCommits = append(
			a.TranscriptRoundAdvanceCommits,
			&protobufs.CeremonyAdvanceRound{
				Commits: []*protobufs.CeremonyTranscriptCommit{
					transcriptCommit,
				},
			},
		)
		a.RoundCount++
	} else {
		for _, c := range a.TranscriptRoundAdvanceCommits[a.RoundCount-1].Commits {
			if bytes.Equal(
				c.ProverSignature.PublicKey.KeyValue,
				transcriptCommit.ProverSignature.PublicKey.KeyValue,
			) {
				return nil
			}
		}
		a.TranscriptRoundAdvanceCommits[a.RoundCount-1].Commits = append(
			a.TranscriptRoundAdvanceCommits[a.RoundCount-1].Commits,
			transcriptCommit,
		)
	}

	return nil
}
