from flask import request, jsonify
from configparser import RawConfigParser
from jellyfin_accounts.jf_api import Jellyfin
import json
import datetime
import secrets
import time
from __main__ import config, config_path, app, g
from __main__ import web_log as log
from jellyfin_accounts.validate_password import PasswordValidator

def resp(success=True, code=500):
    if success:
        r = jsonify({'success': True})
        r.status_code = 200
    else:
        r = jsonify({'success': False})
        r.status_code = code
    return r


def checkInvite(code, delete=False):
    current_time = datetime.datetime.now()
    try:
        with open(config['files']['invites'], 'r') as f:
            invites = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        invites = {'invites': []}
    valid = False
    for index, i in enumerate(invites['invites']):
        expiry = datetime.datetime.strptime(i['valid_till'],
                                            '%Y-%m-%dT%H:%M:%S.%f')
        if current_time >= expiry:
            log.debug(('Housekeeping: Deleting old invite ' +
                       invites['invites'][index]['code']))
            del invites['invites'][index]
        else:
            if i['code'] == code:
                valid = True
                if delete:
                    del invites['invites'][index]
    with open(config['files']['invites'], 'w') as f:
        f.write(json.dumps(invites, indent=4, default=str))
    return valid


jf = Jellyfin(config['jellyfin']['server'],
              config['jellyfin']['client'],
              config['jellyfin']['version'],
              config['jellyfin']['device'],
              config['jellyfin']['device_id'])

from jellyfin_accounts.login import auth

attempts = 0
success = False
while attempts != 3:
    try:
        jf.authenticate(config['jellyfin']['username'],
                        config['jellyfin']['password'])
        success = True
        log.info(('Successfully authenticated with ' +
                 config['jellyfin']['server']))
        break
    except Jellyfin.AuthenticationError:
        attempts += 1
        log.error(('Failed to authenticate with ' +
                   config['jellyfin']['server'] +
                   '. Retrying...'))
        time.sleep(5)

if not success:
    log.error('Could not authenticate after 3 tries.')


def switchToIds():
    try:
        with open(config['files']['emails'], 'r') as f:
            emails = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        emails = {}
    users = jf.getUsers(public=False)
    new_emails = {}
    match = False
    for key in emails:
        for user in users:
            if user['Name'] == key:
                match = True
                new_emails[user['Id']] = emails[key]
            elif user['Id'] == key:
                new_emails[user['Id']] = emails[key]
    if match:
        from pathlib import Path
        email_file = Path(config['files']['emails']).name
        log.info((f'{email_file} modified to use userID instead of ' +
                  'usernames. These will be used in future.'))
        emails = new_emails
        with open(config['files']['emails'], 'w') as f:
            f.write(json.dumps(emails, indent=4))


# Temporary, switches emails.json over from using Usernames to User IDs.
switchToIds()

if config.getboolean('password_validation', 'enabled'):
    validator = PasswordValidator(config['password_validation']['min_length'],
                                  config['password_validation']['upper'],
                                  config['password_validation']['lower'],
                                  config['password_validation']['number'],
                                  config['password_validation']['special'])
else:
    validator = PasswordValidator(0, 0, 0, 0, 0)


@app.route('/getRequirements', methods=['GET', 'POST'])
def getRequirements():
    data = request.get_json()
    log.debug('Password Requirements requested')
    if checkInvite(data['code']):
        return jsonify(validator.getCriteria())


@app.route('/newUser', methods=['GET', 'POST'])
def newUser():
    data = request.get_json()
    log.debug('Attempted newUser')
    if checkInvite(data['code']):
        validation = validator.validate(data['password'])
        valid = True
        for criterion in validation:
            if validation[criterion] is False:
                valid = False
        if valid:
            log.debug('User password valid')
            try: 
                jf.authenticate(config['jellyfin']['username'],
                                config['jellyfin']['password'])
                user = jf.newUser(data['username'], data['password'])
            except Jellyfin.UserExistsError:
                error = 'User already exists with name '
                error += data['username']
                log.debug(error)
                return jsonify({'error': error})
            except:
                return jsonify({'error': 'Unknown error'})
            checkInvite(data['code'], delete=True)
            if user.status_code == 200:
                try:
                    with open(config['files']['user_template'], 'r') as f:
                        default_policy = json.load(f)
                    jf.setPolicy(user.json()['Id'], default_policy)
                except:
                    log.debug('setPolicy failed')
                if config.getboolean('password_resets', 'enabled'):
                    try:
                        with open(config['files']['emails'], 'r') as f:
                            emails = json.load(f)
                    except (FileNotFoundError, json.decoder.JSONDecodeError):
                        emails = {}
                    emails[user.json()['Id']] = data['email']
                    with open(config['files']['emails'], 'w') as f:
                        f.write(json.dumps(emails, indent=4))
                    log.debug('Email address stored')
                log.info('New User created.')
            else:
                log.error(f'New user creation failed: {user.status_code}')
                return resp(False)
        else:
            log.debug('User password invalid')
        return jsonify(validation)
    else:
        log.debug('Attempted newUser unauthorized')
        return resp(False, code=401)


@app.route('/generateInvite', methods=['GET', 'POST'])
@auth.login_required
def generateInvite():
    current_time = datetime.datetime.now()
    data = request.get_json()
    delta = datetime.timedelta(hours=int(data['hours']),
                               minutes=int(data['minutes']))
    invite = {'code': secrets.token_urlsafe(16)}
    log.debug(f'Creating new invite: {invite["code"]}')
    valid_till = current_time + delta
    invite['valid_till'] = valid_till.strftime('%Y-%m-%dT%H:%M:%S.%f')
    if 'email' in data and config.getboolean('invite_emails', 'enabled'):
        address = data['email']
        invite['email'] = address
        log.info(f'Sending invite to {address}')
        method = config['email']['method']
        if method == 'mailgun':
            from jellyfin_accounts.email import Mailgun
            email = Mailgun(address)
        elif method == 'smtp':
            from jellyfin_accounts.email import Smtp
            email = Smtp(address)
        email.construct_invite({'expiry': valid_till,
                                'code': invite['code']})
        email.send()
    try:
        with open(config['files']['invites'], 'r') as f:
            invites = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        invites = {'invites': []}
    invites['invites'].append(invite)
    with open(config['files']['invites'], 'w') as f:
        f.write(json.dumps(invites, indent=4, default=str))
    log.info(f'New invite created: {invite["code"]}')
    return resp()


@app.route('/getInvites', methods=['GET'])
@auth.login_required
def getInvites():
    log.debug('Invites requested')
    current_time = datetime.datetime.now()
    try:
        with open(config['files']['invites'], 'r') as f:
            invites = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        invites = {'invites': []}
    response = {'invites': []}
    for index, i in enumerate(invites['invites']):
        expiry = datetime.datetime.strptime(i['valid_till'],
                                            '%Y-%m-%dT%H:%M:%S.%f')
        if current_time >= expiry:
            log.debug(('Housekeeping: Deleting old invite ' +
                       invites['invites'][index]['code']))
            del invites['invites'][index]
        else:
            valid_for = expiry - current_time
            invite = {'code': i['code'],
                      'hours': valid_for.seconds//3600,
                      'minutes': (valid_for.seconds//60) % 60}
            if 'email' in i:
                invite['email'] = i['email']
            response['invites'].append(invite)
    with open(config['files']['invites'], 'w') as f:
        f.write(json.dumps(invites, indent=4, default=str))
    return jsonify(response)


@app.route('/deleteInvite', methods=['POST'])
@auth.login_required
def deleteInvite():
    code = request.get_json()['code']
    try:
        with open(config['files']['invites'], 'r') as f:
            invites = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        invites = {'invites': []}
    for index, i in enumerate(invites['invites']):
        if i['code'] == code:
            del invites['invites'][index]
            with open(config['files']['invites'], 'w') as f:
                f.write(json.dumps(invites, indent=4, default=str))
    log.info(f'Invite deleted: {code}')
    return resp()


@app.route('/getToken')
@auth.login_required
def get_token():
    token = g.user.generate_token()
    return jsonify({'token': token.decode('ascii')})


@app.route('/modifyConfig', methods=['POST'])
@auth.login_required
def modifyConfig():
    log.info('Config modification requested')
    data = request.get_json()
    temp_config = RawConfigParser(comment_prefixes='/',
                                  allow_no_value=True)
    temp_config.read(config_path)
    for section in data:
        if section in temp_config:
            for item in data[section]:
                if item in temp_config[section]:
                    temp_config[section][item] = data[section][item]
                    data[section][item] = True
                    log.debug(f'{section}/{item} modified')
                else:
                    data[section][item] = False
                    log.debug(f'{section}/{item} does not exist in config')
    with open(config_path, 'w') as config_file:
        temp_config.write(config_file)
    log.debug('Config written')
    return jsonify(data)


@app.route('/getUsers', methods=['GET', 'POST'])
@auth.login_required
def getUsers():
    log.debug('User and email list requested')
    try:
        with open(config['files']['emails'], 'r') as f:
            emails = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        emails = {}
    response = {'users': []}
    jf.authenticate(config['jellyfin']['username'],
                    config['jellyfin']['password'])
    users = jf.getUsers(public=False)
    for user in users:
        entry = {'name': user['Name']}
        if user['Id'] in emails:
            entry['email'] = emails[user['Id']]
        response['users'].append(entry)
    return jsonify(response)

@app.route('/modifyUsers', methods=['POST'])
@auth.login_required
def modifyUsers():
    data = request.get_json()
    log.debug('User and email list modification requested')
    try:
        with open(config['files']['emails'], 'r') as f:
            emails = json.load(f)
    except (FileNotFoundError, json.decoder.JSONDecodeError):
        emails = {}
    jf.authenticate(config['jellyfin']['username'],
                    config['jellyfin']['password'])
    for key in data:
        uid = jf.getUsers(key, public=False)['Id']
        log.debug(f'Email for user "{key}" modified')
        emails[uid] = data[key]
    try:
        with open(config['files']['emails'], 'w') as f:
            f.write(json.dumps(emails, indent=4))
        return resp()
    except:
        return resp(success=False)


