import base64
import hashlib
import random
import re
import string
import dokk.sparql as sparql

from bottle import request, response
from datetime import datetime, timezone
from dokk import random, settings
from string import Template

def exists(username):
    """
    Check if a User already exists.
    
    :param username: Username of the user.
    """
    
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>

        ASK {
                [] a dokk:User ;
                dokk:username "$username" .
            }
    """).substitute(username = sparql.escape_literal(username))
    
    response = sparql.query_instance(query)
    
    return response['boolean']

def create(username, password):
    """
    Add a new User.
    
    :param username: Username of the new user.
    :param password: Password for the new user.
    """
    
    salt = random.ascii_string(64)
    
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>
        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
        
        INSERT  {
                    <dokk:/$uri> a dokk:User ;
                    dokk:username "$username" ;
                    dokk:password ?password ;
                    dokk:salt "$salt" ;
                    dokk:registration_datetime "$registered"^^xsd:dateTime ;
                    dokk:banned false .
                }
        WHERE   {
                    BIND(SHA512(CONCAT("$salt", ":", "$password")) AS ?password)
                };
    """).substitute(
        uri      = sparql.escape_term(username),
        username = sparql.escape_literal(username),
        password = sparql.escape_literal(password),
        salt     = sparql.escape_literal(salt),
        registered = sparql.escape_literal(datetime.now(timezone.utc).isoformat()))
    
    sparql.update_instance(query)

def get(username):
    """
    Retrieve a single user.
    
    :param username: Username of the user to be retrieved.
    """
    
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>

        DESCRIBE *
        WHERE   {
                    ?s a dokk:User ;
                    dokk:username "$username" .
                }
    """).substitute(username = sparql.escape_literal(username))
    
    response = sparql.query_instance(query)
    
    return response

def get_from_password(username, password):
    """
    Retrieve a single user with password validation.
    
    :param username: Username of the user to be retrieved.
    :param password: User password.
    """
    
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>

        DESCRIBE *
        WHERE   {
                    ?s a dokk:User ;
                    dokk:username "$username" ;
                    dokk:salt ?salt ;
                    dokk:password ?password .
                    
                    FILTER (?password = SHA512(CONCAT(?salt, ":", "$password")))
                }
    """).substitute(
        username = sparql.escape_literal(username),
        password = sparql.escape_literal(password))
    
    response = sparql.query_instance(query)
    
    return response

def get_from_session():
    """
    Restore a user by reading its session token in the cookie.
    """
    
    session_token = request.get_cookie(
        key    = settings['session.name'],
        secret = settings['cookies.secret'])
    
    if not session_token:
        return None
    
    # Retrieve from database
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>

        DESCRIBE *
        WHERE   {
                    ?s a dokk:User ;
                    dokk:session_token "$token" .
                }
    """).substitute(token = sparql.escape_literal(session_token))
    
    user = sparql.query_instance(query)
    
    if 'username' not in user:
        return None
    
    return user

def is_banned(username):
    """
    Check if a user is banned or not.
    
    :param username: Username of the user.
    """
    
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>

        ASK {
                [] a dokk:User ;
                dokk:username "$username" ;
                dokk:banned true .
            }
    """).substitute(username = sparql.escape_literal(username))
    
    response = sparql.query_instance(query)
    
    return response['boolean']
    
def is_signedin():
    """
    Check if a user is signed in or not.
    """
    
    return get_from_session() is not None

def start_session(username, remember=False):
    """
    Start a new browser session.
    
    :param username: Username of the user.
    :param remember: "Remember me" or expire with browser.
    """
    
    session_token = random.ascii_string(64)
    
    # Create session cookie
    response.set_cookie(
        name     = settings['session.name'],
        value    = session_token,
        secret   = settings['cookies.secret'],
        path     = '/',
        # When to end the session
        max_age  = settings['session.remember_me'] if remember else None,
        # HTTPS only
        secure   = False,
        # Do not allow JavaScript to read this cookie
        httponly = True)
    
    # Store session token in the database, so that we can identify the signed in
    # user from the session token stored in the cookie
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>
        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
        
        DELETE  { ?s dokk:session_token ?o }
        WHERE   {
                    ?s a dokk:User ;
                    dokk:username "$username" ;
                    ?p ?o .
                };
        
        INSERT  { ?s dokk:session_token "$token" }
        WHERE   {
                    ?s a dokk:User ;
                    dokk:username "$username" .
                };
    """).substitute(
        username = sparql.escape_literal(username),
        token    = sparql.escape_literal(session_token))
    
    sparql.update_instance(query)

# End the current open session
def end_session():
    # Read user
    user = get_from_session()
    
    if not user:
        return
    
    # Delete session token from the database
    query = Template("""
        PREFIX : <dokk:/>
        PREFIX dokk: <https://ontology.dokk.org/>
        PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
        
        DELETE  { ?s dokk:session_token ?o }
        WHERE   {
                    ?s a dokk:User ;
                    dokk:username "$username" ;
                    ?p ?o .
                }
    """).substitute(username = sparql.escape_literal(user['username']))
    
    sparql.update_instance(query)
    
    # Delete user cookie containing session token
    response.delete_cookie(settings['session.name'])
