import json
import hashlib
import logging
import socket
import secrets
import platform

from aiohttp import web

import user_login

try:
    # pylint: disable=import-error
    import pam #apt-get install python-pampy or pacman -S python-pam on ArchLinuxARM
except:
   pam = None

AUTH_ERROR_SLEEP = 1
COOKIE_NAME_HTTP = 'token'
COOKIE_NAME_HTTPS = 'token-https'

def check_auth(request, auth_token_arg=None):
    if not request.app['3dp_app'].settings['remote_control']['allow_no_auth_localhost'] or not request_is_from_localhost(request):
        for auth_token in (auth_token_arg,
                      request.headers.get('Authorization'),
                      request.cookies.get(COOKIE_NAME_HTTP),
                      request.cookies.get(COOKIE_NAME_HTTPS),
                      request.query.get('auth_token')):
            if auth_token in request.app['cookie_tokens']:
                return
        raise web.HTTPUnauthorized()

def check_ssh_creds(login, password):
    # check login using RPi's root credentials
    platform_name = platform.system().lower()
    if platform_name.startswith('linux') and pam:
        try:
            return pam.authenticate(login, password, service='sudo') #service sudo is essential for this to work on RPi
        except:
            logging.getLogger('check_ssh_creds').exception("PAM authentication error")
    return False

def check_cloud_creds(login, password, hashed=False):
    if not hashed:
        password = hashlib.sha256(password.encode('utf-8')).hexdigest()
    stored_login, stored_password = user_login.UserLogin.load_login()
    if stored_login:
        return login == stored_login and password == stored_password

def create_auth_token():
    return secrets.token_hex()

def create_auth_response(request):
    token = create_auth_token()
    request.app['cookie_tokens'].append(token)
    response = web.json_response({'auth_check': True, 'auth_token': token}, status=200)
    if request.secure:
        cookie_name = COOKIE_NAME_HTTPS
    else:
        cookie_name = COOKIE_NAME_HTTP
    response.set_cookie(cookie_name, token, httponly=False, secure=False)
    return response

def request_is_from_localhost(request):
    try:
        host = request.host.split(':')[0]
    except:
        host = "127.0.0.1"
    try:
        #without conversions below an error like '127.0.0.1' != 'localhost' is possible
        remote_addr = socket.gethostbyname(request.remote)
    except:
        remote_addr = request.remote
    try:
        # sometimes gethostbyname is failing with UnicodeError or socket.error
        # that is a fallback to old way of comparing host and remote
        host_addr = socket.gethostbyname(host)
    except:
        # sometimes gethostbyname is failing with UnicodeError or socket.error
        # that is a fallback to old way of comparing host and remote
        host_addr = host
    return remote_addr == host_addr

async def auth(request):
    auth_token = request.headers.get('Authorization')
    if not auth_token:
        if request.secure:
            cookie_name = COOKIE_NAME_HTTPS
        else:
            cookie_name = COOKIE_NAME_HTTP
        auth_token = request.cookies.get(cookie_name)
    if auth_token:
        if auth_token in request.app['cookie_tokens']:
            return web.json_response({'auth_check': True})
    if not request.app['3dp_app'].settings['remote_control']['auto_auth_localhost'] or \
            not request_is_from_localhost(request):
        auth_dict = {}
        try:
            auth_dict = await request.json()
            if not isinstance(auth_dict, dict):
                raise TypeError()
        except:
            return web.json_response({'auth_check': False, \
                    'error': 'auth body should be a json object(dict)'}, status=401)
            #  if request.content_type == 'application/x-www-form-urlencoded':
            #      auth_dict = request.query
            #  elif request.method == 'POST':
            #      try:
            #          auth_dict = await request.post()
            #          print('Auth dict')
            #          if not isinstance(auth_dict, dict):
            #              raise TypeError()
            #      except:
            #          return web.json_response({'auth_check': False, \
            #                  'error': 'auth post body should be json object(dict) or form data'}, status=401)
        if auth_dict and auth_dict.get(auth_token) in request.app['cookie_tokens']:
            return web.json_response({'auth_check': True})
        password = auth_dict.get('password')
        if password is None:
            return web.json_response({'auth_check': False, \
                    'error': 'auth json should be dict with valid password field'}, status=401)
        email = auth_dict.get('email', auth_dict.get('login'))
        if email:
            if not check_cloud_creds(email, password):
                return web.json_response({'auth_check': False, \
                        'error': 'invalid 3dprinteros account credentials'}, status=401)
        else:
            ssh_login = auth_dict.get('ssh_login') 
            if not ssh_login:
                return web.json_response({'error': \
                        'auth json lacks one of fields(with non empty values): email or ssh_login'}, status=401)
            if not request.app['3dp_app'].settings.get('auth_using_os'):
                return web.json_response({'auth_check': False, \
                'error': 'OS auth using ssh_login field is disabled in ~/.3dprinteros/user_settings.json:["remote_control"]["auth_using_os"]'}, \
                status=401)
            if not check_ssh_creds(request.app['3dp_app'], ssh_login, password): 
                return web.json_response({'auth_check': False, \
                        'error': 'invalid OS credentials or such auth type not supported on server OS'}, status=401)
    return create_auth_response(request)
