import pytest
import base64
import os
from secrets import token_urlsafe
from werkzeug.security import check_password_hash
from datetime import datetime
from ..db.models import User, Action, ActionType
from .utils import make_random_email


FAKE_CONFIRMATION_URL = 'https://fake.com/confirm'


def test_signup(client, username, email, password, bio):
    cur_time = datetime.now()
    resp = client.post('/users', json=dict(
        username=username, email=email, password=password, bio=bio,
        email_confirmation_url=FAKE_CONFIRMATION_URL
    ))
    assert resp['id'] > 0

    # convert the time response and assert that the user was just created
    created_time = datetime.strptime(resp['created_at'], '%Y-%m-%dT%H:%M:%S')
    assert (created_time - cur_time).total_seconds() < 5
    assert resp['updated_at'] == resp['created_at']
    assert resp['username'] == username
    assert resp['bio'] == bio
    assert resp['avatar_url'].startswith('https://secure.gravatar')
    assert resp['data'] == []

    # assert that certain fields never show up
    assert 'password' not in resp.keys()
    assert 'email' not in resp.keys()
    assert 'password_hash' not in resp.keys()

    # but since we have DB access, actually verify the password was saved
    # correctly, and that no 'password' field exists on the user
    user = User.query.filter_by(id=resp['id']).first()
    assert pytest.raises(AttributeError, lambda: user.password)
    assert check_password_hash(user.password_hash, password)
    assert user.email == email
    assert user.bio == bio
    assert user.ec_token != ""
    assert user.is_email_confirmed is False
    assert user.is_admin is False

    # verify action was added
    action = Action.query.filter_by(type=ActionType.SIGNUP, user_id=user.id).first()
    assert action is not None

    # user can not sign up twice
    resp = client.post('/users', raw=True, json=dict(
        username=username, email=email, password=password, bio=bio,
        email_confirmation_url=FAKE_CONFIRMATION_URL
    ))
    assert resp.status_code == 500
    resp = resp.get_json()
    assert 'exists' in resp['error']


def test_cant_signup_with_existing_username(client, user, email, password, bio):
    resp = client.post('/users', raw=True, json=dict(
        username=user.username,
        email=email,
        password=password,
        bio=bio,
        email_confirmation_url=FAKE_CONFIRMATION_URL
    ))
    assert resp.status_code == 500
    resp = resp.get_json()
    assert 'already exists' in resp['error']
    assert user.username == resp['username']


def test_cant_signup_with_existing_email(client, user, username, password, bio):
    resp = client.post('/users', raw=True, json=dict(
        username=username,
        email=user.email,
        password=password,
        bio=bio,
        email_confirmation_url=FAKE_CONFIRMATION_URL
    ))
    assert resp.status_code == 500
    resp = resp.get_json()
    assert 'already exists' in resp['error']
    assert user.email == resp['email']


def test_signup_missing_fields(client, username, email, password):
    resp = client.post('/users', raw=True, json=dict()).get_json()
    assert resp['status'] == 400
    assert 'username' in resp['detail']

    resp = client.post('/users', raw=True, json=dict(
        username=username
    )).get_json()
    assert resp['status'] == 400
    assert 'email' in resp['detail']

    resp = client.post('/users', raw=True, json=dict(
        username=username, email=email
    )).get_json()
    assert resp['status'] == 400
    assert 'password' in resp['detail']

    resp = client.post('/users', raw=True, json=dict(
        username=username, email=email, password=password
    )).get_json()
    assert resp['status'] == 400
    assert 'bio' in resp['detail']


def test_signup_long_username(client, email, password):
    username = token_urlsafe(100)
    resp = client.post('/users', raw=True, json=dict(
        username=username, email=email, password=password
    )).get_json()
    assert resp['status'] == 400
    assert 'too long' in resp['detail']


@pytest.mark.skip(reason="TODO")
def test_signup_sql_injection_attack(client):
    pass


def test_user_detail(client, user):
    resp = client.get('/users/%s' % user.id, headers=user.auth_headers)

    assert 'password' not in resp.keys()
    assert 'email' not in resp.keys()
    assert 'password_hash' not in resp.keys()

    assert resp['id'] == user.id
    assert resp['username'] == user.username

    # verify action was added
    action = Action.query.filter_by(type=ActionType.LIST_USER, user_id=user.id, obj1_id=user.id).first()
    assert action is not None


def test_can_get_user_detail_for_other_user(client, user, other_user):
    resp = client.get('/users/%s' % user.id, headers=other_user.auth_headers)

    assert 'password' not in resp.keys()
    assert 'email' not in resp.keys()
    assert 'password_hash' not in resp.keys()

    assert resp['id'] == user.id
    assert resp['username'] == user.username

    # verify action was added
    action = Action.query.filter_by(type=ActionType.LIST_USER, user_id=other_user.id, obj1_id=user.id).first()
    assert action is not None


def test_cant_get_user_that_doesnt_exist(client, user):
    resp = client.get('/users/10000000', headers=user.auth_headers, raw=True).get_json()
    assert resp['status'] == 404


def test_user_confirm_email(client, user):
    assert user.is_email_confirmed is False
    resp = client.get('/users/confirm_email/%s' % user.ec_token.token)
    assert resp['confirmed']
    assert user.is_email_confirmed is True
    assert user.ec_token is None

    # verify action was added
    action = Action.query.filter_by(type=ActionType.CONFIRM_EMAIL, user_id=user.id).first()
    assert action is not None


def test_user_confirm_email_fails_bad_token(client, user):
    assert user.is_email_confirmed is False
    resp = client.get('/users/confirm_email/%s' % 'foobar', raw=True).get_json()
    assert resp['status'] == 404
    assert 'token' in resp['detail']
    assert user.is_email_confirmed is False
    assert user.ec_token.token is not None


def test_user_can_update_details(client, user):
    new_email = make_random_email()
    new_password = 'foobarbaz'

    user.is_email_confirmed = True
    assert user.email != new_email
    assert user.check_password(new_password) is False

    client.post('/users/%s' % user.id, headers=user.auth_headers, json=dict(
        email=new_email, password=new_password
    ))

    user = User.query.filter_by(id=user.id).first()
    assert user.email == new_email
    assert user.is_email_confirmed is False
    assert user.check_password(new_password)

    # verify action was added
    action = Action.query.filter_by(type=ActionType.UPDATE_USER, user_id=user.id, obj1_id=user.id).first()
    assert action is not None


def test_user_cannot_update_details_for_other_user(client, user, other_user):
    new_email = make_random_email()
    new_password = 'foobarbaz'

    user.is_email_confirmed = True

    resp = client.post('/users/%s' % other_user.id, headers=user.auth_headers, raw=True, json=dict(
        email=new_email, password=new_password
    )).get_json()

    assert resp['status'] == 401
    assert 'permission' in resp['detail']

    other_user = User.query.filter_by(id=other_user.id).first()
    assert user.email != new_email
    assert not user.check_password(new_password)

    # verify action was not added
    action = Action.query.filter_by(type=ActionType.UPDATE_USER, user_id=user.id, obj1_id=other_user.id).first()
    assert action is None


def test_admin_can_update_details_for_other_user(client, admin_user, other_user):
    new_email = make_random_email()
    new_password = 'foobarbaz'

    other_user.is_email_confirmed = True

    client.post('/users/%s' % other_user.id, headers=admin_user.auth_headers, raw=True, json=dict(
        email=new_email, password=new_password
    ))

    user = User.query.filter_by(id=other_user.id).first()
    assert user.email == new_email
    assert user.is_email_confirmed is False
    assert user.check_password(new_password)

    # verify action was added
    action = Action.query.filter_by(type=ActionType.UPDATE_USER, user_id=admin_user.id, obj1_id=other_user.id).first()
    assert action is not None


@pytest.mark.skip(reason="TODO")
def test_user_can_update_one_field_at_a_time():
    pass


@pytest.mark.skip(reason="TODO")
def test_user_cant_update_certain_fields():
    # TODO like username etc
    pass


def test_admin_can_promote_user(client, user, admin_user):
    assert not user.is_admin
    client.post('/users/%s/promote' % user.id, headers=admin_user.auth_headers)
    assert User.query.filter_by(id=user.id).first().is_admin

    # verify action was added
    action = Action.query.filter_by(type=ActionType.PROMOTE_USER, user_id=admin_user.id, obj1_id=user.id).first()
    assert action is not None


def test_non_admin_cant_promote_user(client, user, other_user):
    assert not user.is_admin
    resp = client.post('/users/%s/promote' % user.id, headers=other_user.auth_headers, raw=True).get_json()
    assert resp['status'] == 401
    assert 'admin' in resp['detail']
    assert not User.query.filter_by(id=user.id).first().is_admin


def test_user_cant_promote_self(client, user):
    assert not user.is_admin
    resp = client.post('/users/%s/promote' % user.id, headers=user.auth_headers, raw=True).get_json()
    assert resp['status'] == 401
    assert 'admin' in resp['detail']
    assert not User.query.filter_by(id=user.id).first().is_admin


def test_upload_avatar(client, user, avatar_png_b64, avatar_post_b64, config):
    assert user.has_avatar is False
    resp = client.post('/users/%s/avatar' % user.id, headers=user.auth_headers, json=dict(
        data=avatar_png_b64,
        type='image/png'
    ))
    assert user.has_avatar is True
    assert resp == user.avatar_url
    img_on_disk = os.path.join(config.AVATAR_PATH, user.avatar_file)
    assert os.path.exists(img_on_disk)
    with open(img_on_disk, 'rb') as img:
        img_b64 = base64.b64encode(img.read()).decode('ascii')
        assert img_b64 == avatar_post_b64

    # verify action was added
    action = Action.query.filter_by(type=ActionType.UPLOAD_AVATAR, user_id=user.id).first()
    assert action is not None


def test_cant_upload_avatar_for_other_user(client, user, other_user, avatar_png_b64, config):
    assert user.has_avatar is False
    resp = client.post('/users/%s/avatar' % user.id, headers=other_user.auth_headers, json=dict(
        data=avatar_png_b64,
        type='image/png'
    ), raw=True).get_json()
    assert resp['status'] == 401
    assert user.has_avatar is False


def test_get_avatar_404s_when_no_avatar(client, user):
    resp = client.get('users/%s/avatar' % user.id, raw=True).get_json()
    assert resp['status'] == 404


def test_get_avatar(client, user, avatar_png_b64, avatar_post_b64):
    assert user.has_avatar is False
    client.post('/users/%s/avatar' % user.id, headers=user.auth_headers, json=dict(
        data=avatar_png_b64,
        type='image/png'
    ))
    resp = client.get('/users/%s/avatar' % user.id, raw=True)
    assert resp.headers['Content-Type'] == 'image/png'
    img_b64 = base64.b64encode(resp.data).decode('ascii')
    assert img_b64 == avatar_post_b64


def test_resend_confirmation_email(client, user):
    resp = client.post('users/resend_confirmation_email', json=dict(
        email_confirmation_url=FAKE_CONFIRMATION_URL
    ), headers=user.auth_headers)
    assert resp['success'] is True

    # verify action was added
    action = Action.query.filter_by(type=ActionType.RESEND_EMAIL_CONFIRM, user_id=user.id).first()
    assert action is not None
