import binascii
import time
from datetime import datetime
from ipaddress import ip_address

import flask

from nyaa import email, forms, models
from nyaa.extensions import db
from nyaa.utils import sha1_hash
from nyaa.views.users import get_activation_link, get_password_reset_link, get_serializer

app = flask.current_app
bp = flask.Blueprint('account', __name__)


@bp.route('/login', methods=['GET', 'POST'])
def login():
    if flask.g.user:
        return flask.redirect(redirect_url())

    form = forms.LoginForm(flask.request.form)
    if flask.request.method == 'POST' and form.validate():
        if app.config['MAINTENANCE_MODE'] and not app.config['MAINTENANCE_MODE_LOGINS']:
            flask.flash(flask.Markup('<strong>Logins are currently disabled.</strong>'), 'danger')
            return flask.redirect(flask.url_for('account.login'))

        username = form.username.data.strip()
        password = form.password.data
        user = models.User.by_username(username)

        if not user:
            user = models.User.by_email(username)

        if not user or password != user.password_hash:
            flask.flash(flask.Markup(
                '<strong>Login failed!</strong> Incorrect username or password.'), 'danger')
            return flask.redirect(flask.url_for('account.login'))

        if user.is_banned:
            ban_reason = models.Ban.banned(user.id, None).first().reason
            ban_str = ('<strong>Login failed!</strong> You are banned with the '
                       'reason "{0}" If you believe that this is a mistake, contact '
                       'a moderator on IRC.'.format(ban_reason))
            flask.flash(flask.Markup(ban_str), 'danger')
            return flask.redirect(flask.url_for('account.login'))

        if user.status != models.UserStatusType.ACTIVE:
            flask.flash(flask.Markup(
                '<strong>Login failed!</strong> Account is not activated.'), 'danger')
            return flask.redirect(flask.url_for('account.login'))

        user.last_login_date = datetime.utcnow()
        user.last_login_ip = ip_address(flask.request.remote_addr).packed
        if not app.config['MAINTENANCE_MODE']:
            db.session.add(user)
            db.session.commit()

        flask.g.user = user
        flask.session['user_id'] = user.id
        flask.session.permanent = True
        flask.session.modified = True

        return flask.redirect(redirect_url())

    return flask.render_template('login.html', form=form)


@bp.route('/logout')
def logout():
    flask.g.user = None
    flask.session.permanent = False
    flask.session.modified = False

    response = flask.make_response(flask.redirect(redirect_url()))
    response.set_cookie(app.session_cookie_name, expires=0)
    return response


@bp.route('/register', methods=['GET', 'POST'])
def register():
    if flask.g.user:
        return flask.redirect(redirect_url())

    form = forms.RegisterForm(flask.request.form)
    if flask.request.method == 'POST' and form.validate():
        user = models.User(username=form.username.data.strip(),
                           email=form.email.data.strip(), password=form.password.data)
        user.registration_ip = ip_address(flask.request.remote_addr).packed
        user.last_login_ip = user.registration_ip
        db.session.add(user)
        db.session.commit()

        if app.config['RAID_MODE_LIMIT_REGISTER']:
            flask.flash(flask.Markup(app.config['RAID_MODE_REGISTER_MESSAGE'] + ' '
                                     'Please <a href="{}">ask a moderator</a> to manually '
                                     'activate your account <a href="{}">\'{}\'</a>.'
                                     .format(flask.url_for('site.help') + '#irchelp',
                                             flask.url_for('users.view_user',
                                                           user_name=user.username),
                                             user.username)), 'warning')

        elif models.RangeBan.is_rangebanned(user.registration_ip):
            flask.flash(flask.Markup('Your IP is blocked from creating new accounts. '
                                     'Please <a href="{}">ask a moderator</a> to manually '
                                     'activate your account <a href="{}">\'{}\'</a>.'
                                     .format(flask.url_for('site.help') + '#irchelp',
                                             flask.url_for('users.view_user',
                                                           user_name=user.username),
                                             user.username)), 'warning')
        else:
            if app.config['USE_EMAIL_VERIFICATION']:  # force verification, enable email
                send_verification_email(user)
                return flask.render_template('waiting.html')
            else:  # disable verification, set user as active and auto log in
                user.status = models.UserStatusType.ACTIVE
                db.session.add(user)
                db.session.commit()
                flask.g.user = user
                flask.session['user_id'] = user.id
                flask.session.permanent = True
                flask.session.modified = True
                return flask.redirect(redirect_url())

    return flask.render_template('register.html', form=form)


@bp.route('/password-reset/<payload>', methods=['GET', 'POST'])
@bp.route('/password-reset', methods=['GET', 'POST'])
def password_reset(payload=None):
    if not app.config['ALLOW_PASSWORD_RESET']:
        return flask.abort(404)

    if flask.g.user:
        return flask.redirect(redirect_url())

    if payload is None:
        form = forms.PasswordResetRequestForm(flask.request.form)
        if flask.request.method == 'POST' and form.validate():
            user = models.User.by_email(form.email.data.strip())
            if user:
                send_password_reset_request_email(user)

            flask.flash(flask.Markup(
                'A password reset request was sent to the provided email, '
                'if a matching account was found.'), 'info')
            return flask.redirect(flask.url_for('main.home'))
        return flask.render_template('password_reset_request.html', form=form)

    else:
        s = get_serializer()
        try:
            request_timestamp, pw_hash, user_id = s.loads(payload)
        except:
            return flask.abort(404)

        user = models.User.by_id(user_id)
        if not user:
            return flask.abort(404)

        # Timeout after six hours
        if (time.time() - request_timestamp) > 6 * 3600:
            return flask.abort(404)

        sha1_password_hash_hash = binascii.hexlify(sha1_hash(user.password_hash.hash)).decode()
        if pw_hash != sha1_password_hash_hash:
            return flask.abort(404)

        form = forms.PasswordResetForm(flask.request.form)
        if flask.request.method == 'POST' and form.validate():
            user.password_hash = form.password.data

            db.session.add(user)
            db.session.commit()

            send_password_reset_email(user)

            flask.flash(flask.Markup('Your password was reset. Log in now.'), 'info')
            return flask.redirect(flask.url_for('account.login'))
        return flask.render_template('password_reset.html', form=form)


@bp.route('/profile', methods=['GET', 'POST'])
def profile():
    if not flask.g.user:
        # so we don't get stuck in infinite loop when signing out
        return flask.redirect(flask.url_for('main.home'))

    form = forms.ProfileForm(flask.request.form)

    if flask.request.method == 'POST':
        if form.authorized_submit and form.validate():
            user = flask.g.user
            new_email = form.email.data.strip()
            new_password = form.new_password.data

            if new_email:
                if form.current_password.data != user.password_hash:
                    flask.flash(flask.Markup(
                        '<strong>Email change failed!</strong> Incorrect password.'), 'danger')
                    return flask.redirect('/profile')
                user.email = form.email.data
                flask.flash(flask.Markup(
                    '<strong>Email successfully changed!</strong>'), 'success')

            if new_password:
                if form.current_password.data != user.password_hash:
                    flask.flash(flask.Markup(
                        '<strong>Password change failed!</strong> Incorrect password.'), 'danger')
                    return flask.redirect('/profile')
                user.password_hash = form.new_password.data
                flask.flash(flask.Markup(
                    '<strong>Password successfully changed!</strong>'), 'success')
            db.session.add(user)
            db.session.commit()
            flask.g.user = user
            return flask.redirect('/profile')

        elif form.submit_settings:
            user = flask.g.user
            if user.preferences is None:
                preferences = models.UserPreferences(user.id)
                db.session.add(preferences)
                db.session.commit()
            user.preferences.hide_comments = form.hide_comments.data
            flask.flash(flask.Markup(
                '<strong>Preferences successfully changed!</strong>'), 'success')
            db.session.add(user)
            db.session.commit()
            flask.g.user = user
            return flask.redirect('/profile')

    return flask.render_template('profile.html', form=form)


def redirect_url():
    next_url = flask.request.args.get('next', '')
    referrer = flask.request.referrer or ''

    target_url = (
        # Use ?next= param if it's a local (/foo/bar) path
        (next_url.startswith('/') and next_url) or
        # Use referrer if it's on our own host
        (referrer.startswith(flask.request.host_url) and referrer)
    )

    # Return the target, avoiding infinite loops
    if target_url and target_url != flask.request.url:
        return target_url

    # Default to index
    return flask.url_for('main.home')


def send_verification_email(user):
    activation_link = get_activation_link(user)

    tmpl_context = {
        'activation_link': activation_link,
        'user': user
    }

    email_msg = email.EmailHolder(
        subject='Verify your {} account'.format(app.config['GLOBAL_SITE_NAME']),
        recipient=user,
        text=flask.render_template('email/verify.txt', **tmpl_context),
        html=flask.render_template('email/verify.html', **tmpl_context),
    )

    email.send_email(email_msg)


def send_password_reset_email(user):
    ''' Alert user that their password has been successfully reset '''

    email_msg = email.EmailHolder(
        subject='Your {} password has been reset'.format(app.config['GLOBAL_SITE_NAME']),
        recipient=user,
        text=flask.render_template('email/reset.txt', user=user),
        html=flask.render_template('email/reset.html', user=user),
    )

    email.send_email(email_msg)


def send_password_reset_request_email(user):
    ''' Send user a password reset link '''
    reset_link = get_password_reset_link(user)

    tmpl_context = {
        'reset_link': reset_link,
        'user': user
    }

    email_msg = email.EmailHolder(
        subject='{} password reset request'.format(app.config['GLOBAL_SITE_NAME']),
        recipient=user,
        text=flask.render_template('email/reset-request.txt', **tmpl_context),
        html=flask.render_template('email/reset-request.html', **tmpl_context),
    )

    email.send_email(email_msg)
