# -*- coding: utf-8 -*-


from copy import deepcopy
import json
import os

import ox

from changelog import add_record
from oxtornado import actions
from utils import update_dict, user_sort_key
from . import models
import settings
import state
import tor_request

import logging
logger = logging.getLogger(__name__)


def init(data):
    '''
        takes {
        }
        returns {
            config
            user
                preferences
                ui
        }
    '''
    response = {}
    if os.path.exists(settings.oml_data_path):
        with open(settings.oml_data_path) as fd:
            config = json.load(fd)
            if not settings.FULLTEXT_SUPPORT:
                config['itemKeys'] = [k for k in config['itemKeys'] if k['id'] != 'fulltext']
    else:
        config = {}
    response['config'] = config
    response['user'] = deepcopy(config['user'])
    response['version'] = settings.MINOR_VERSION
    if settings.preferences:
        response['user']['preferences'] = settings.preferences
    response['user']['id'] = settings.USER_ID
    response['user']['online'] = state.online
    if settings.ui:
        for f in settings.ui['filters']:
            if f['id'] == 'classification':
                f['id'] = 'categories'
                settings.ui._save()
        response['user']['ui'] = settings.ui
    return response
actions.register(init)

def public_init(data):
    response = init(data)
    name = response['user']['preferences']['username']
    response['user'] = response['config']['user']
    response['user']['preferences']['username'] = name
    response['user']['ui']['page'] = ''
    response['user']['ui']['showFolder'] = {'': True}
    response['readOnly'] = True
    for page in response['config']['pages']:
        if page['id'] == 'preferences':
            #page['parts'] = [p for p in page['parts'] if p['id'] in ('appearance', 'extensions')]
            page['parts'] = [p for p in page['parts'] if p['id'] in ('appearance',)]
    return response
actions.register(public_init, action='init', version='public')


def setPreferences(data):
    '''
        takes {
            key: value,
            'sub.key': value
        }
    '''

    change_contact = 'contact' in data and \
            data['contact'] != settings.preferences['contact']
    change_username = 'username' in data and \
            data['username'] != settings.preferences['username']
    change_autostart = 'autostart' in data and \
            data['autostart'] != settings.preferences['autostart']
    if 'libraryPath' in data and \
            data['libraryPath'] != settings.preferences['libraryPath']:
        change_path = [settings.preferences['libraryPath'], data['libraryPath']]
    else:
        change_path = False
    update_dict(settings.preferences, data)
    if 'username' in data:
        u = state.user()
        u.update_name()
        u.save()
    if change_username:
        add_record('editusername', data['username'])
        if state.nodes and state.nodes.local:
            state.nodes.local._update_if_ip_changed()
    if change_contact:
        add_record('editcontact', data['contact'])
    if change_path:
        state.tasks.queue('changelibrarypath', change_path)
    if change_autostart:
        import integration
        if settings.preferences['autostart']:
            integration.install_autostart()
        else:
            integration.uninstall_autostart()
    return settings.preferences
actions.register(setPreferences, cache=False)

def resetUI(data):
    '''
        takes {
        }
    '''
    ui_json = os.path.join(settings.data_path, 'ui.json')
    if os.path.exists(ui_json):
        os.unlink(ui_json)
    settings.ui = settings.pdict(ui_json, settings.config['user']['ui'])
    return settings.ui
actions.register(resetUI, cache=False)

def setUI(data):
    '''
        takes {
            key: value,
            'sub.key': value
        }
    '''
    update_dict(settings.ui, data)
    #return settings.ui
    return {}
actions.register(setUI, cache=False)


def getUsers(data):
    '''
        returns {
            users: []
        }
    '''
    users = []
    ids = set()
    local = set()
    for u in models.User.query.filter(models.User.id != settings.USER_ID).all():
        users.append(u.json())
        ids.add(u.id)
    if state.nodes:
        for id in state.nodes.local:
            if id not in ids:
                n = state.nodes.local[id].copy()
                n['online'] = True
                n['name'] = n['username']
                users.append(n)
            local.add(id)
    for n in users:
        n['local'] = n['id'] in local

    users.sort(key=user_sort_key)
    return {
        "users": users
    }
actions.register(getUsers)

def getUsersPublic(data):
    return {
        'users': []
    }
actions.register(getUsersPublic, 'getUsers', version='public')


def getLists(data):
    '''
        returns {
            lists: []
        }
    '''
    from item.models import Item
    from user.models import List
    lists = []
    lists.append({
        'id': '',
        'items': Item.query.count(),
        'name': 'Libraries',
        'type': 'libraries',
        'user': None,
    })
    List.get_or_create(':Inbox')
    for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)):
        lists += u.lists_json()
    return {
        'lists': lists
    }
actions.register(getLists)

def getListsPublic(data):
    '''
        returns {
            lists: []
        }
    '''
    from item.models import Item
    from sqlalchemy.sql import operators
    user = state.user()
    lists = []
    lists.append({
        'id': '',
        'items': user.items.count(),
        'name': 'Libraries',
        'type': 'libraries',
        'user': None,
    })
    lists += user.lists_json()
    return {
        'lists': lists
    }
actions.register(getListsPublic, 'getLists', version='public')

def validate_query(query):
    validate_conditions(query['conditions'])

def validate_conditions(conditions):
    for c in conditions:
        if 'conditions' in c:
            if list(sorted(c.keys())) != ['conditions', 'operator']:
                raise Exception('invalid query condition', c)
            validate_conditions(c['conditions'])
        else:
            if list(sorted(c.keys())) != ['key', 'operator', 'value']:
                raise Exception('invalid query condition', c)
            if c['key'] == 'list' and ':' not in c['value']:
                raise Exception('invalid query condition', c)

def addList(data):
    '''
        takes {
            name
            items
            query
        }
    '''
    logger.debug('addList %s', data)
    user_id = settings.USER_ID
    if 'query' in data:
        validate_query(data['query'])
    if data['name']:
        l = models.List.create(user_id, data['name'], data.get('query'))
        if 'items' in data:
            l.add_items(data['items'])
        return l.json()
    else:
        raise Exception('name not set')
    return {}
actions.register(addList, cache=False)


def editList(data):
    '''
        takes {
            id
            name
            query
        }
    '''
    logger.debug('editList %s', data)
    l = models.List.get_or_create(data['id'])
    name = l.name
    if 'name' in data:
        l.name = data['name']
    if 'query' in data and l.type != 'smart':
        raise Exception('query only for smart lists')
    if 'query' in data and l.type == 'smart':
        validate_query(data['query'])
        l._query = data['query']
    if l.type == 'static' and name != l.name:
        add_record('editlist', name, {'name': l.name})
    l.save()
    return l.json()
actions.register(editList, cache=False)


def removeList(data):
    '''
        takes {
            id
        }
    '''
    l = models.List.get(data['id'])
    if l and l.name != 'Inbox':
        l.remove()
    return {}
actions.register(removeList, cache=False)


def addListItems(data):
    '''
        takes {
            list
            items
        }
    '''
    if data['list'] == ':':
        from item.models import Item
        for item_id in data['items']:
            i = Item.get(item_id, for_update=True)
            i.queue_download()
            i.update()
    elif data['list'] and (data['list'].startswith(':') or data['list'].endswith(':')):
        l = models.List.get_or_create(data['list'])
        if l:
            if l.name in ('Inbox', '') and l.user_id != settings.USER_ID:
                state.tasks.queue('upload', {
                    'user': l.user_id,
                    'items': data['items']
                })
            else:
                l.add_items(data['items'])
            state.cache.clear('group:')
            return l.json()
    return {}
actions.register(addListItems, cache=False)


def removeListItems(data):
    '''
        takes {
            list
            items
        }
    '''
    l = models.List.get(data['list'])
    if l:
        l.remove_items(data['items'])
        state.cache.clear('group:')
        return l.json()
    return {}
actions.register(removeListItems, cache=False)


def sortLists(data):
    '''
        takes {
            ids
        }
    '''
    n = 0
    logger.debug('sortLists %s', data)
    lists = []
    for id in data['ids']:
        l = models.List.get(id)
        l.index_ = n
        n += 1
        if l.type == 'static' and l.name not in ('', 'Inbox'):
            lists.append(l.name)
        state.db.session.add(l)
    state.db.session.commit()
    if lists:
        add_record('orderlists', lists)
    return {}
actions.register(sortLists, cache=False)


def editUser(data):
    '''
        takes {
            id
            nickname
        }
    '''
    if 'nickname' in data:
        p = models.User.get_or_create(data['id'])
        if data['nickname']:
            p.info['nickname'] = data['nickname']
        elif 'nickname' in p.info:
            del p.info['nickname']
        old = p.nickname
        p.update_name()
        if old != p.nickname:
            models.List.rename_user(old, p.nickname)
        p.save()
        return p.json()
    return {}
actions.register(editUser, cache=False)

def sortUsers(data):
    '''
        takes {
            ids
        }
    '''
    n = 0
    for id in data['ids']:
        u = models.User.get(id)
        u.info['index'] = n
        n += 1
        state.db.session.add(u)
    state.db.session.commit()
    if state.tasks:
        state.tasks.queue('syncmetadata')
    return {}
actions.register(sortUsers, cache=False)

def requestPeering(data):
    '''
        takes {
            id
            message
            nickname (optional)
        }
    '''
    if len(data.get('id', '')) != 16:
        logger.debug('invalid user id')
        return {}
    u = models.User.get_or_create(data['id'])
    u.pending = 'sent'
    u.queued = True
    u.info['message'] = data.get('message', '')
    if data.get('nickname'):
        u.info['nickname'] = data.get('nickname', '')
        u.update_name()
    u.save()
    state.nodes.peer_queue(u.id, 'peering', 'requestPeering')
    return {}
actions.register(requestPeering, cache=False)


def acceptPeering(data):
    '''
        takes {
            id
            message
        }
    '''
    if len(data.get('id', '')) != 16:
        logger.debug('invalid user id')
        return {}
    logger.debug('acceptPeering... %s', data)
    u = models.User.get_or_create(data['id'])
    u.info['message'] = data.get('message', '')
    u.update_peering(True)
    state.nodes.peer_queue(u.id, 'peering', 'acceptPeering')
    return {}
actions.register(acceptPeering, cache=False)


def rejectPeering(data):
    '''
        takes {
            id
            message
        }
    '''
    if len(data.get('id', '')) not in (16, 43):
        logger.debug('invalid user id')
        return {}
    u = models.User.get_or_create(data['id'])
    u.info['message'] = data.get('message', '')
    u.update_peering(False)
    state.nodes.peer_queue(u.id, 'peering', 'rejectPeering')
    return {}
actions.register(rejectPeering, cache=False)


def removePeering(data):
    '''
        takes {
            id
            message
        }
    '''
    if len(data.get('id', '')) not in (16, 43):
        logger.debug('invalid user id')
        return {}
    u = models.User.get(data['id'], for_update=True)
    if u:
        u.info['message'] = data.get('message', '')
        u.update_peering(False)
        state.nodes.peer_queue(u.id, 'peering', 'removePeering')
    return {}
actions.register(removePeering, cache=False)


def cancelPeering(data):
    '''
        takes {
        }
    '''
    if len(data.get('id', '')) != 16:
        logger.debug('invalid user id')
        return {}
    u = models.User.get_or_create(data['id'])
    u.info['message'] = data.get('message', '')
    u.update_peering(False)
    state.nodes.peer_queue(u.id, 'peering', 'cancelPeering')
    return {}
actions.register(cancelPeering, cache=False)


def getActivity(data):
    '''
        return {
            activity
            progress
        }
    '''
    return state.activity
actions.register(getActivity, cache=False)

def contact(data):
    '''
        return {
        }
    '''
    response = {}
    url = 'http://rnogx24drkbnrxa3.onion/contact'
    headers = {
        'User-Agent': settings.USER_AGENT,
    }
    try:
        data = json.dumps(data).encode()
        opener = tor_request.get_opener()
        opener.addheaders = list(zip(headers.keys(), headers.values()))
        r = opener.open(url, data)
        error = r.status != 200
    except:
        logger.debug('failed to send contact', exc_info=True)
        error = True
    if error:
        response = {'error': True}
    return response
actions.register(contact, cache=False)