import hashlib
import hmac

from pylons import config

try:
    from hmac import compare_digest
except ImportError:  # Python < 3.3
    def compare_digest(a, b):
        # We'll just try emulating it here and hope that the network noise
        # is sufficient and the Python interpreter isn't too clever

        if type(a) != type(b) or len(a) != len(b):
            # This conforms to the doc, which says:
            # > If a and b are of different lengths, or if an error occurs, a
            # > timing attack could theoretically reveal information about the
            # > types and lengths of a and b - but not their values.
            return False
        res = 1
        for achar, bchar in zip(a, b):
            # The "and" operator short-circuits!
            res = res & int(achar == bchar)
        return res == 1


def get_secret(config=config, key=None):
    search_keys = [
        'adhocracy.crypto.secret',
        'beaker.session.secret',
        'adhocracy.auth.secret',
    ]
    if key is not None:
        search_keys.insert(0, key)
    for k in search_keys:
        if config.get(k):
            assert config[k] != 'autogenerated'
            res = config[k]
            if not isinstance(res, bytes):
                res = res.encode('ascii')
            return res
    raise Exception('No secret configured!')


def _sign(val, secret, salt):
    hm = hmac.new(secret + salt, val, hashlib.sha256)
    digest = hm.hexdigest()
    return digest.encode('ascii')


def sign(val, secret=None, salt=b''):
    if secret is None:
        secret = get_secret()
    assert isinstance(secret, bytes)
    assert isinstance(val, bytes)
    assert isinstance(salt, bytes)

    return _sign(val, secret, salt) + b'!' + val


def verify(signed, secret=None, salt=b''):
    if secret is None:
        secret = get_secret()
    assert isinstance(secret, bytes)
    assert isinstance(signed, bytes)
    assert isinstance(salt, bytes)

    signature, _, val = signed.partition(b'!')
    correct_signature = _sign(val, secret, salt)
    if compare_digest(signature, correct_signature):
        return val
    else:
        raise ValueError(salt.decode('ascii') + u' MAC verification failed')
