import getpass
import json
import pathlib
import random
import string
import sys

# name of the file where we store the pw database
PWDB_FLNAME = pathlib.Path('pwdb.json')

# list of valid characters for salt (only ASCII letters + digits + punctuation)
CHARS = string.ascii_letters + string.digits + string.punctuation

# length of salt
SALT_LENGTH = 5

def get_credentials():
    # get input from terminal
    username = input('Enter your username: ')
    # get password using the appropriate module, so that typed characters are not
    # echoed to the terminal
    password = getpass.getpass('Enter your password: ')
    return (username, password)

def authenticate(username, pass_text, pwdb):
    # get the salt from the database
    salt = pwdb[username][1]
    # calculate hash and compare with stored hash
    return pwhash(pass_text, salt) == pwdb[username][0]

def add_user(username, pwdb):
    # do not try to add a username twice
    if username in pwdb:
        raise Exception(f'Username already exists [{username}]!')
    else:
        password = getpass.getpass(f'Enter password for {username}: ')
        salt = get_salt()
        pwdb[username] = (pwhash(password,salt), salt)
    return pwdb

def read_pwdb(pwdb_path):
    if not pwdb_path.exists():
        pwdb = {}
    else:
        with open(pwdb_path, 'rt') as pwdb_file:
            pwdb = json.load(pwdb_file)
    return pwdb

def write_pwdb(pwdb, pwdb_path):
    with open(pwdb_path, 'wt') as pwdb_file:
        json.dump(pwdb, pwdb_file)

def pwhash(pass_text, salt):
    # simple additive hash -> very insecure!
    hash_ = 0
    full_pass_text = pass_text + salt
    for idx, char in enumerate(full_pass_text):
        # use idx as a multiplier, so that shuffling the characters returns a
        # different hash
        hash_ += (idx+1)*ord(char)
    return hash_

def get_salt():
    salt_chars = random.choices(CHARS, k=SALT_LENGTH)
    return ''.join(salt_chars)

def main(pwdb_path):
    # load the password database from file
    pwdb = read_pwdb(pwdb_path)

    # if we are passed an argument, we want to add a new user
    if len(sys.argv) > 1:
        pwdb = add_user(sys.argv[1], pwdb)
        write_pwdb(pwdb, pwdb_path)
        return

    # ask for credentials
    username, password = get_credentials()
    if username not in pwdb or not authenticate(username, password, pwdb):
        print('Wrong username or password!')
    else:
        print('Successfully authenticated!')

    return

if __name__ == '__main__':
    main(PWDB_FLNAME)

