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

# Copyright (C) 2014-2018 Avencall
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

from sqlalchemy.sql import and_, or_, func

from xivo_dao.alchemy.agentfeatures import AgentFeatures
from xivo_dao.alchemy.entity import Entity
from xivo_dao.alchemy.userfeatures import UserFeatures
from xivo_dao.alchemy.linefeatures import LineFeatures
from xivo_dao.alchemy.usersip import UserSIP
from xivo_dao.alchemy.voicemail import Voicemail
from xivo_dao.alchemy.user_line import UserLine
from xivo_dao.alchemy.extension import Extension
from xivo_dao.resources.utils.search import SearchSystem
from xivo_dao.resources.utils.search import SearchConfig

import logging
logger = logging.getLogger(__name__)


def get_configuration(table=UserFeatures):
    configuration = {
        'table': table,
        'columns': {
            'id': table.id,
            'uuid': table.uuid,
            'firstname': table.firstname,
            'lastname': table.lastname,
            'fullname': (table.firstname + " " + table.lastname),
            'caller_id': table.callerid,
            'description': table.description,
            'userfield': table.userfield,
            'email': table.email,
            'mobile_phone_number': table.mobilephonenumber,
            'music_on_hold': table.musiconhold,
            'outgoing_caller_id': table.outcallerid,
            'preprocess_subroutine': table.preprocess_subroutine,
            'entity': Entity.name,
            'voicemail_number': Voicemail.mailbox,
            'provisioning_code': LineFeatures.provisioning_code,
            'exten': Extension.exten,
            'extension': Extension.exten,
            'context': Extension.context,
            'enabled': table.enabled,
            'agentid': table.agentid
        },
        'search': [
            'fullname',
            'description',
            'userfield',
            'email'
        ],
        'search_weights': {
            'fullname': 5,
            'description': 1,
            'userfield': 1,
            'email': 1
        },
        'default_sort': 'lastname',
        'similarity_threshold': 0.1,
    }
    return configuration

class UserSearchConfig(SearchConfig):

    def __init__(self, **config):
        basic_config = {
            _: config.get(_) for _ in ('table', 'columns', 'default_sort', 'search', 'sort')
        }
        super(UserSearchConfig, self).__init__(**basic_config)
        self.similarity_threshold = config.get('similarity_threshold', 0.1)
        self._search_weights = config.get('search_weights')

    def search_columns_and_weights(self):
        if self._search_weights is None:
            return []
        return [
            (self._columns[k], v) for k, v in self._search_weights.items()
        ]

class UserSearchSystem(SearchSystem):

    def __init__(self, config):
        super(UserSearchSystem, self).__init__(config)
        self.searchers = [
            UserSearchFulltextAnd(config),
            UserSearchFulltextOr(config),
            UserSearchFuzzy(config),
            UserSearchLike(config),
        ]

    def search_from_query(self, query, parameters=None):
        term = parameters.get('search')
        query = self._search_on_extension(query)

        if term is None:
            logger.debug('No search expression. Using common search.')
            return super(UserSearchSystem, self).search_from_query(query, parameters)
        else:
            for searcher in self.searchers:
                logger.debug(searcher.id_text)
                result = searcher.search_from_query(query, parameters)
                if len(result[0]) > 0:
                    return result
            return list(), 0

    @staticmethod
    def _search_on_extension(query, table=UserFeatures):
        return (query
                .outerjoin(Entity,
                           table.entity_id == Entity.id)
                .outerjoin(UserLine,
                           UserLine.user_id == table.id)
                .outerjoin(LineFeatures,
                           and_(LineFeatures.id == UserLine.line_id,
                                LineFeatures.commented == 0))
                .outerjoin(Extension,
                           and_(UserLine.extension_id == Extension.id,
                                Extension.commented == 0))
                .outerjoin(Voicemail,
                           and_(table.voicemailid == Voicemail.uniqueid,
                                Voicemail.commented == 0))
                .outerjoin(AgentFeatures,
                           table.agentid == AgentFeatures.id)
                .outerjoin(UserSIP,
                           and_(LineFeatures.protocol == 'sip',
                                LineFeatures.protocolid == UserSIP.id)))


class UserSearchLike(SearchSystem):
    id_text = 'Common Search (%like%)'

    def search_from_query(self, query, parameters=None):
        return super(UserSearchLike, self).search_from_query(query, parameters)


class UserSearchFulltextAnd(SearchSystem):
    SEARCH_TERM_CONCAT_CHAR = '&'
    id_text = 'Fulltext Search "&"'

    def search_from_query(self, query, parameters=None):
        self.term = parameters.get('search')
        return super(UserSearchFulltextAnd, self).search_from_query(query, parameters)

    def _fulltext_search_term(self):
        return (' %s ' % self.SEARCH_TERM_CONCAT_CHAR).join(self.term.split())

    def _filter(self, query, term=None):
        return query.filter(or_(*[
            _.match(term, postgresql_regconfig='pg_catalog.simple')
            for _ in self.config.all_search_columns()
        ]))

    def _sort(self, query, order=None, direction='asc'):
        sort_def = self.config.search_columns_and_weights()
        if not sort_def:
            return query
        return query.order_by(self.SORT_DIRECTIONS[direction](sum(
            [
                func.ts_rank(func.to_tsvector(column), func.to_tsquery(self.term)) * weight
                for column, weight in sort_def
            ]
        )))


class UserSearchFulltextOr(UserSearchFulltextAnd):
    SEARCH_TERM_CONCAT_CHAR = '|'
    id_text = 'Fulltext Search "|"'


class UserSearchFuzzy(SearchSystem):
    id_text = 'Fuzzy Search'

    def search_from_query(self, query, parameters=None):
        self.term = parameters.get('search')
        return super(UserSearchFuzzy, self).search_from_query(query, parameters)

    def _similarities(self):
        return [
            func.similarity(_, self.term)
            for _ in self.config.all_search_columns()
        ]

    def _filter(self, query, term=None):
        return query.filter(or_(*[_ > self.config.similarity_threshold for _ in self._similarities()]))

    def _sort(self, query, order=None, direction='asc'):
        return query.order_by(func.greatest(*self._similarities()).desc())


user_search = UserSearchSystem(UserSearchConfig(**get_configuration()))
