# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime
import json
import hashlib

import sqlalchemy as sa

from changelog import Changelog
from db import MutableDict
import db
import json_pickler
import settings
import state
import utils

import logging
logger = logging.getLogger(__name__)

class User(db.Model):
    __tablename__ = 'user'

    created = sa.Column(sa.DateTime())
    modified = sa.Column(sa.DateTime())

    id = sa.Column(sa.String(43), primary_key=True)
    info = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))

    nickname = sa.Column(sa.String(256), unique=True)

    pending = sa.Column(sa.String(64)) # sent|received
    queued = sa.Column(sa.Boolean())
    peered = sa.Column(sa.Boolean())
    online = sa.Column(sa.Boolean())

    def __repr__(self):
        return self.id

    @classmethod
    def get(cls, id):
        return cls.query.filter_by(id=id).first()

    @classmethod
    def get_or_create(cls, id):
        user = cls.get(id)
        if not user:
            user = cls(id=id, peered=False, online=False)
            user.info = {}
            if state.nodes and state.nodes._local and id in state.nodes._local._nodes:
                user.info['local'] = state.nodes._local._nodes[id]
                user.info['username'] = user.info['local']['username']
            user.update_name()
            user.save()
        return user

    def save(self):
        state.db.session.add(self)
        state.db.session.commit()

    @property
    def name(self):
        name = self.nickname if self.id != settings.USER_ID else ''
        return name

    @property
    def library(self):
        l = List.get_or_create(self.id, '')
        if l.index_ != -1:
            l.index_ = -1
            l.save()
        return l

    def json(self):
        j = {}
        if self.info:
            j.update(self.info)
        j['id'] = self.id
        if self.pending:
            j['pending'] = self.pending
        j['peered'] = self.peered
        j['online'] = self.is_online()
        j['nickname'] = self.info.get('nickname')
        j['username'] = self.info.get('username') if self.id != settings.USER_ID else settings.preferences['username']
        j['name'] = self.name
        return j

    def is_online(self):
        return state.nodes and state.nodes.is_online(self.id)

    def lists_json(self):
        self.library
        return [l.json() for l in self.lists.order_by('index_')]

    def update_peering(self, peered, username=None):
        was_peering = self.peered
        if peered:
            logging.debug('update_peering, pending: %s queued: %s', self.pending, self.queued)
            self.queued = self.pending != 'sent'
            self.pending = ''
            if username:
                self.info['username'] = username
            self.update_name()
            # FIXME: need to set peered to False to not trigger changelog event
            # before other side receives acceptPeering request
            self.peered = False
            self.save()
            if not was_peering:
                Changelog.record(state.user(), 'addpeer', self.id, self.nickname)
            self.peered = True
            self.save()
        else:
            self.pending = ''
            self.peered = False
            self.queued = False
            self.update_name()
            self.save()
            List.query.filter_by(user_id=self.id).delete()
            for i in self.items:
                i.users.remove(self)
                if not i.users:
                    i.delete()
            Changelog.query.filter_by(user_id=self.id).delete()
            Metadata.query.filter_by(user_id=self.id).delete()
            if self.name in settings.ui['showFolder']:
                del settings.ui['showFolder'][self.name]
                settings.ui._save()
            self.save()
            if was_peering:
                Changelog.record(state.user(), 'removepeer', self.id)
        self.save()

    def update_name(self):
        if self.id == settings.USER_ID:
            name = settings.preferences.get('username', 'anonymous')
        else:
            name = self.info.get('nickname') or self.info.get('username') or 'anonymous'
        nickname = name
        n = 2
        while self.query.filter_by(nickname=nickname).filter(User.id!=self.id).first():
            nickname = '%s [%d]' % (name, n)
            n += 1
        self.nickname = nickname

    def migrate_id(self, service_id):
        if len(service_id) == 16:
            statements = [
                "DELETE FROM user WHERE id = '{nid}'",
                "UPDATE user SET id = '{nid}' WHERE id = '{oid}'",
                "UPDATE list SET user_id = '{nid}' WHERE user_id = '{oid}'",
                "UPDATE useritem SET user_id = '{nid}' WHERE user_id = '{oid}'",
                "UPDATE changelog SET user_id = '{nid}' WHERE user_id = '{oid}'",
            ]
            with db.session() as session:
                for sql in statements:
                    session.connection().execute(sql.format(oid=self.id, nid=service_id))
                session.commit()

list_items = sa.Table('listitem', db.metadata,
    sa.Column('list_id', sa.Integer(), sa.ForeignKey('list.id')),
    sa.Column('item_id', sa.String(32), sa.ForeignKey('item.id'))
)

class List(db.Model):
    __tablename__ = 'list'

    id = sa.Column(sa.Integer(), primary_key=True)
    name = sa.Column(sa.String())
    index_ = sa.Column(sa.Integer())

    type = sa.Column(sa.String(64))
    _query = sa.Column('query', MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))

    user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id'))
    user = sa.orm.relationship('User', backref=sa.orm.backref('lists', lazy='dynamic'))

    items = sa.orm.relationship('Item', secondary=list_items,
            backref=sa.orm.backref('lists', lazy='dynamic'))

    @classmethod
    def get(cls, user_id, name=None):
        if name is None:
            user_id, name = cls.get_user_name(user_id)
        return cls.query.filter_by(user_id=user_id, name=name).first()

    @classmethod
    def get_user_name(cls, user_id):
        nickname, name = user_id.split(':', 1)
        if nickname:
            user = User.query.filter_by(nickname=nickname).first()
            user_id = user.id
        else:
            user_id = settings.USER_ID
        return user_id, name

    @classmethod
    def get_or_create(cls, user_id, name=None, query=None):
        if name is None:
            user_id, name = cls.get_user_name(user_id)
        l = cls.get(user_id, name)
        if not l:
            l = cls.create(user_id, name, query)
        return l

    @classmethod
    def create(cls, user_id, name, query=None):
        prefix = name
        n = 2
        while cls.get(user_id, name):
            name = '%s [%s]' % (prefix, n)
            n += 1
        l = cls(user_id=user_id, name=name)
        l._query = query
        l.type = 'smart' if l._query else 'static'
        l.index_ = cls.query.filter_by(user_id=user_id).count()
        state.db.session.add(l)
        state.db.session.commit()
        if user_id == settings.USER_ID:
            if not l._query and name != '':
                Changelog.record(state.user(), 'addlist', l.name)
        return l

    @classmethod
    def rename_user(cls, old, new):
        for l in cls.query.filter(cls._query!=None):

            def update_conditions(conditions):
                changed = False
                for c in conditions:
                    if 'conditions' in c:
                        changed = update_conditions(c['conditions'] )
                    else:
                        if c.get('key') == 'list' and c.get('value', '').startswith('%s:' % old):
                            c['value'] = '%s:%s' % new, c['value'].split(':', 1)[1]
                            changed = True
                return changed

            if update_conditions(l._query.get('conditions', [])):
                l.save()

    def add_items(self, items):
        from item.models import Item
        for item_id in items:
            i = Item.get(item_id)
            if i and i not in self.items:
                self.items.append(i)
                if self.user_id == settings.USER_ID:
                    i.queue_download()
                i.update()
        state.db.session.add(self)
        state.db.session.commit()
        if self.user_id == settings.USER_ID and self.name != '':
            Changelog.record(self.user, 'addlistitems', self.name, items)

    def get_items(self):
        from item.models import Item
        if self.type == 'smart':
            return Item.find({'query': self._query})
        else:
            return self.user.items.join(Item.lists, aliased=True).filter(List.id == self.id)

    def remove_items(self, items):
        from item.models import Item
        for item_id in items:
            i = Item.get(item_id)
            if i in self.items:
                self.items.remove(i)
            i.update()
        state.db.session.add(self)
        state.db.session.commit()
        if self.user_id == settings.USER_ID and self.name != '':
            Changelog.record(self.user, 'removelistitems', self.name, items)

    def remove(self):
        if not self._query:
            for i in self.items:
                self.items.remove(i)
        if not self._query:
            if self.user_id == settings.USER_ID and self.name != '':
                Changelog.record(self.user, 'removelist', self.name)
        state.db.session.delete(self)
        state.db.session.commit()

    @property
    def public_id(self):
        id = ''
        if self.user_id != settings.USER_ID:
            id += self.user.nickname
        id = '%s:%s' % (id, self.name)
        return id

    @property
    def find_id(self):
        id = ''
        if self.user_id != settings.USER_ID:
            id += self.user_id
        id = '%s:%s' % (id, self.id)
        return id

    def __repr__(self):
        return self.public_id

    def items_count(self):
        return self.get_items().count()

    def json(self):
        r = {
            'id': self.public_id,
            'user': self.user.name,
            'name': self.name,
            'index': self.index_,
            'items': self.items_count(),
            'type': self.type
        }
        if self.name == '':
            r['name'] = 'Library'
            r['type'] = 'library'
            del r['index']
        if self.type == 'smart':
            r['query'] = self._query
        return r

    def save(self):
        state.db.session.add(self)
        state.db.session.commit()

class Metadata(db.Model):
    __tablename__ = 'user_metadata'

    created = sa.Column(sa.DateTime())
    modified = sa.Column(sa.DateTime())

    id = sa.Column(sa.Integer(), primary_key=True)
    item_id = sa.Column(sa.String(32))
    user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id'))
    data_hash = sa.Column(sa.String(40), index=True)
    data = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))

    def __repr__(self):
        return '{item}/{user}'.format(item=self.item_id, user=self.user_id)

    @property
    def timestamp(self):
        return utils.datetime2ts(self.modified)

    @classmethod
    def get(cls, user_id, item_id):
        return cls.query.filter_by(user_id=user_id,item_id=item_id).first()

    @classmethod
    def get_or_create(cls, user_id, item_id, data=None, commit=True):
        m = cls.get(user_id=user_id, item_id=item_id)
        if not m:
            m = cls(user_id=user_id, item_id=item_id)
            m.created = datetime.utcnow()
            if data:
                m.data = data
            else:
                m.data = {}
            m.save(commit)
        elif data:
            m.edit(data, commit)
        return m

    def save(self, commit=True):
        self.modified = datetime.utcnow()
        self.data_hash = hashlib.sha1(json.dumps(self.data, ensure_ascii=False, sort_keys=True).encode()).hexdigest()
        state.db.session.add(self)
        if commit:
            state.db.session.commit()

    def edit(self, data, commit=True):
        changes = {}
        if 'isbn' in data and isinstance(data['isbn'], list):
            isbns = [utils.to_isbn13(isbn) for isbn in data['isbn']]
            isbns = [isbn for isbn in isbns if isbn]
            if isbns:
                data['isbn'] = isbns[0]
            else:
                del data['isbn']
        for key in data:
            if key == 'id':
                continue
            if data[key] != self.data.get(key):
                self.data[key] = data[key]
                changes[key] = data[key]
        if changes:
            self.save(commit)
        return changes

    def delete(self):
        state.db.session.delete(self)
        state.db.session.commit()