from django.db import models
from .mixcrypt import MixCrypt
from base import mods
from base.models import Auth, Key
from base.serializers import AuthSerializer
from django.conf import settings
# number of bits for the key, all auths should use the same number of bits
B = settings.KEYBITS
[docs]class Mixnet(models.Model):
    """
    Django Model representing a Mixnet entity in the context of electronic voting.
    Attributes:
        voting_id (PositiveIntegerField): Identifier for the voting instance.
        auth_position (PositiveIntegerField): Position of the authorization in the mixnet.
        auths (ManyToManyField): Relation to multiple Auth instances.
        key (ForeignKey): Foreign key to a Key instance, nullable.
        pubkey (ForeignKey): Foreign key to a Key instance for public key, nullable.
    """
    voting_id = models.PositiveIntegerField()
    auth_position = models.PositiveIntegerField(default=0)
    auths = models.ManyToManyField(Auth, related_name="mixnets")
    key = models.ForeignKey(Key, blank=True, null=True,
                            related_name="mixnets",
                            on_delete=models.SET_NULL)
    pubkey = models.ForeignKey(Key, blank=True, null=True,
                               related_name="mixnets_pub",
                               on_delete=models.SET_NULL)
    def __str__(self):
        """
        Returns a string representation of the Mixnet instance.
        :return: A formatted string representing the Mixnet instance.
        :rtype: str
        """
        auths = ", ".join(a.name for a in self.auths.all())
        return "Voting: {}, Auths: {}\nPubKey: {}".format(self.voting_id,
                                                          auths, self.pubkey)
[docs]    def shuffle(self, msgs, pk):
        """
        Shuffles the provided messages using the mixnet's cryptographic settings.
        :param msgs: The messages to shuffle.
        :type msgs: list
        :param pk: Public key used in the shuffling process.
        :type pk: Key
        :return: Shuffled messages.
        :rtype: list
        """
        crypt = MixCrypt(bits=B)
        k = crypt.setk(self.key.p, self.key.g, self.key.y, self.key.x)
        return crypt.shuffle(msgs, pk) 
[docs]    def decrypt(self, msgs, pk, last=False):
        """
        Decrypts the provided messages using the mixnet's cryptographic settings.
        :param msgs: The messages to decrypt.
        :type msgs: list
        :param pk: Public key used in the decryption process.
        :type pk: Key
        :param last: Indicates if this is the last decryption step.
        :type last: bool
        :return: Decrypted messages.
        :rtype: list
        """
        crypt = MixCrypt(bits=B)
        k = crypt.setk(self.key.p, self.key.g, self.key.y, self.key.x)
        return crypt.shuffle_decrypt(msgs, last) 
[docs]    def gen_key(self, p=0, g=0):
        """
        Generates a cryptographic key for the mixnet.
        :param p: Prime number, part of the cryptographic key.
        :type p: int, optional
        :param g: Generator number, part of the cryptographic key.
        :type g: int, optional
        """
        crypt = MixCrypt(bits=B)
        if self.key:
            k = crypt.setk(self.key.p, self.key.g, self.key.y, self.key.x)
        elif (not g or not p):
            k = crypt.genk()
            key = Key(p=int(k.p), g=int(k.g), y=int(k.y), x=int(k.x))
            key.save()
            self.key = key
            self.save()
        else:
            k = crypt.getk(p, g)
            key = Key(p=int(k.p), g=int(k.g), y=int(k.y), x=int(k.x))
            key.save()
            self.key = key
            self.save() 
[docs]    def chain_call(self, path, data):
        """
        Makes a chained API call to the next authorization in the mixnet.
        :param path: The API path for the call.
        :type path: str
        :param data: Data to be sent in the API call.
        :type data: dict
        :return: The response from the API call or None.
        :rtype: Response or None
        """
        next_auths = self.next_auths()
        data.update({
            "auths": AuthSerializer(next_auths, many=True).data,
            "voting": self.voting_id,
            "position": self.auth_position + 1,
        })
        if next_auths:
            auth = next_auths.first().url
            r = mods.post('mixnet', entry_point=path,
                          baseurl=auth, json=data)
            return r
        return None 
[docs]    def next_auths(self):
        """
        Retrieves the next set of authorizations in the mixnet.
        :return: The next set of Auth instances.
        :rtype: QuerySet
        """
        next_auths = self.auths.filter(me=False)
        if self.auths.count() == next_auths.count():
            next_auths = next_auths[1:]
        return next_auths