from itertools import permutations
from hashlib import md5
from time import time
from random import shuffle, choice
    
class VoteCodeObfuscator(object):
    """Provides the obfuscation means for the interceptable traffic"""
    
    def __init__(self, options=None, variants=None):
        """provides basic interfaces for unit testing"""
        self.coded_options = None
        self.variants = variants
        self.options = options
    
    def encode_options(self):
        """Create pseudo-random option keys"""
        salted = []
        for i, opt in enumerate(self.options):
            salted.append(md5(str(i) + str(time())).hexdigest())
        self.coded_options = salted
    
    def create_variants(self):
        """Creates vote-code combinations"""
        temp_variants = []
        salted = []
        for opt in self.options:
            salted.append(md5(opt + str(time())).hexdigest())
        shuffle(self.options)
        for variant in permutations(salted):
            temp_variants.append(variant)
        self.variants = dict(zip(
                [md5(str(x)).hexdigest() for x in temp_variants],
                temp_variants))
        
    def deploy(self):
        """Returns a random variant for deployment"""
        key = choice(self.variants.keys())
        return {"key":key, "options":self.variants[key]}
        
    def decode(self, combination_key, coded_option):
        """Returns the decoded option code
        If the inputs are expired returns False"""
        if combination_key not in self.variants.keys():
            return False
        variant = self.variants[combination_key]
        if coded_option not in variant:
            return False
        return self.options[variant.index(coded_option)]

"""
Explanation: Vote-code Obfuscation
As we are not able to properly encrypt the traffic,
let's make the vote-code interception more uncomfortable
and effort-intensive.
The traffic interception tool may catch the options list
when it is deployed to a client and the vote-code when a user votes. 
By randomizing vote-codes on a daily basis we'll make it hard
enough to track the votes history of an individual machine or
at scale. It is not impossible but becomes a costly IT that
should be hard to justify.
This combined with the fact that the vote may only be tight 
to a machine but not the individual makes the obfuscation 
mechanism sufficient for the use case.
End of explanation
"""


moods_obfuscator = VoteCodeObfuscator(
    options=["opt1", "opt2", "opt3", "opt4"]) 
moods_obfuscator.create_variants()

"""
Explanation: Application scenario:
The code below is run by a worker (independent process) on a day change event. The resulting variants map is then saved to the disk so that any worker process can access it.
End of explanation
"""


moods_obfuscator.deploy()

"""
Explanation: Each time a client requiests moods widget deployment, the server will reply with a random variant like below:
End of explanation
"""


print moods_obfuscator.decode(
    'b55645bd979c9dc9f1a8e18289d4f4d8', 
    '396f87befcc04b908055d403f5c4ee49')

"""
Explanation: When a user votes, the client sends back the variant code and the option code. A worker picks up the message and decodes it to get the actual opton code. An option code is then used to perform +1 operation on the column within the daily metrics that matches the option key
End of explanation
"""


print moods_obfuscator.decode(
    '968c48a1a75ff8beabd6f4ecf0cbf0dc', 
    'edda6cc6dbc632384664964259d6b694')

"""
Explanation: if a user sends the vote from a widget session that was deployed say a day ago decoding will fail and return False. The same obfuscation logic is used for the "why-bad" poll.
End of explanation
"""


