import base64
import os
import flask

from werkzeug.exceptions import NotFound, Unauthorized, BadRequest, InternalServerError
from sqlalchemy import exc
from PIL import Image
from io import BytesIO

from ..db import db
from ..config import get_config
from ..db.models import User, EmailConfirmationToken, Action, ActionType
from .auth import auth
from .util import get_user


def signup(body):
    try:
        user = User.create(body.get('username'), body.get('email'),
                           body.get('password'), body.get('bio'))
    except exc.IntegrityError:
        return dict(
            error=f'Username or e-mail already exists',
            username=body.get('username'),
            email=body.get('email')
        ), 500

    user.ec_token.send_email(body.get('email_confirmation_url'))

    Action.create(ActionType.SIGNUP, user)
    return user.to_obj(whos_asking=user)


def confirm_email(token):
    try:
        user = EmailConfirmationToken.confirm(token)
        Action.create(ActionType.CONFIRM_EMAIL, user)
    except Exception as e:
        raise NotFound(str(e))

    return {'confirmed': True}


@auth()
def resend_confirmation(auth_user, body):
    auth_user.ec_token.send_email(body.get('email_confirmation_url'))
    Action.create(ActionType.RESEND_EMAIL_CONFIRM, auth_user)
    return {'success': True}


@auth()
@get_user()
def detail(user, auth_user):
    Action.create(ActionType.LIST_USER, auth_user, user)
    return user.to_obj(whos_asking=auth_user)


@auth()
@get_user()
def update(user, auth_user, body):
    if not auth_user.is_admin and user.id != auth_user.id:
        raise Unauthorized("You don't have permission to update that user")

    if body.get('email'):
        user.update_email(body.get('email'))
    if body.get('password'):
        user.set_password(body.get('password'))
    db.session.commit()

    Action.create(ActionType.UPDATE_USER, auth_user, user)
    return user.to_obj(whos_asking=auth_user)


@auth(admin=True)
@get_user()
def promote(user, auth_user, body):
    user.is_admin = True
    db.session.commit()
    Action.create(ActionType.PROMOTE_USER, auth_user, user)
    return user.to_obj(whos_asking=auth_user)


@auth()
@get_user()
def upload_avatar(user, auth_user, body):
    if user.id != auth_user.id:
        raise Unauthorized("You can't change the avatar of another user")

    mime_type = body.get('type')
    if mime_type not in ['image/png', 'image/gif', 'image/jpeg', 'image/jpg']:
        raise BadRequest(f'Only PNG, GIF, or JPG image types are supported. '
                         f'You tried to upload a {mime_type}')

    try:
        img = Image.open(BytesIO(base64.b64decode(body.get('data'))))
    except Exception:
        raise BadRequest('There was some error decoding your image data')

    png_info = {}
    if mime_type == 'image/png':
        png_info = img.info
    img.thumbnail((400, 400), Image.ANTIALIAS)
    new_img = BytesIO()
    img.save(new_img, format='PNG', **png_info)

    config = get_config()
    if config.AVATAR_STORAGE == 'local':
        if not os.path.exists(config.AVATAR_PATH):
            os.makedirs(config.AVATAR_PATH)

        img_path = os.path.join(config.AVATAR_PATH, user.avatar_file)
        with open(img_path, 'wb') as out:
            out.write(new_img.getvalue())
    else:
        new_img.seek(0)
        blob = user.get_storage_blob()
        blob.upload_from_file(new_img)
        blob.make_public()

    if not user.has_avatar:
        user.has_avatar = True
        db.session.commit()

    Action.create(ActionType.UPLOAD_AVATAR, user)

    return user.avatar_url


@get_user()
def get_avatar(user):
    config = get_config()

    if config.AVATAR_STORAGE != 'local':
        raise Exception('Cannot get avatar data via this API in production')

    if not user.has_avatar:
        raise NotFound()

    if not os.path.exists(os.path.join(config.AVATAR_PATH, user.avatar_file)):
        raise NotFound()

    return flask.send_from_directory(config.AVATAR_PATH, user.avatar_file)
