# -*- coding: UTF-8 -*-

# Copyright (C) 2015-2016 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import os
from contextlib import contextmanager

import sqlalchemy as sa
from sqlalchemy.sql import text


class DbHelper(object):
    TEMPLATE = "xivotemplate"

    @classmethod
    def build(cls, user, password, host, port, db):
        tpl = "postgresql://{user}:{password}@{host}:{port}"
        uri = tpl.format(user=user,
                         password=password,
                         host=host,
                         port=port)
        return cls(uri, db)

    def __init__(self, uri, db):
        self.uri = uri
        self.db = db

    def create_engine(self, db=None, isolate=False):
        db = db or self.db
        uri = "{}/{}".format(self.uri, db)
        if isolate:
            return sa.create_engine(uri, isolation_level='AUTOCOMMIT')
        return sa.create_engine(uri)

    def connect(self, db=None):
        db = db or self.db
        return self.create_engine(db).connect()

    def recreate(self):
        engine = self.create_engine("postgres", isolate=True)
        connection = engine.connect()
        q1 = text("""
            SELECT pg_terminate_backend(pg_stat_activity.pid)
            FROM pg_stat_activity
            WHERE pg_stat_activity.datname = :db
            AND pid <> pg_backend_pid()
        """)
        connection.execute(q1, {"db": self.db})
        q2 = text(f"""DROP DATABASE IF EXISTS {self.db}""")
        connection.execute(q2)
        q3 = text(f"""CREATE DATABASE {self.db} TEMPLATE :template""")
        connection.execute(q3, {"template": self.TEMPLATE})
        connection.close()

    def execute(self, query, **kwargs):
        with self.connect() as connection:
            connection.execute(text(query), kwargs)

    @contextmanager
    def queries(self):
        with self.connect() as connection:
            yield DatabaseQueries(connection)


class DatabaseQueries(object):

    def __init__(self, connection):
        self.connection = connection

    def insert_queue(self, name='myqueue', number='3000', context='default'):
        queue_query = text("""
        INSERT INTO queuefeatures (name, displayname, number, context)
        VALUES (:name, :displayname, :number, :context)
        RETURNING id
        """)

        queue_id = (self.connection
                    .execute(queue_query,
                             {"name": name,
                             "displayname": name,
                             "number": number,
                             "context": context})
                    .scalar())

        self.insert_extension(number, context, 'queue', queue_id)

        func_key_id = self.insert_func_key('speeddial', 'queue')
        self.insert_destination('queue', 'queue_id', func_key_id, queue_id)

        return queue_id

    def insert_func_key(self, func_key_type, destination_type):
        func_key_query = text("""
        INSERT INTO func_key (type_id, destination_type_id)
        VALUES (
        (SELECT id FROM func_key_type WHERE name = :func_key_type),
        (SELECT id FROM func_key_destination_type WHERE name = :destination_type)
        )
        RETURNING id
        """)

        return (self.connection
                .execute(func_key_query,
                         {"func_key_type": func_key_type,
                         "destination_type": destination_type})
                .scalar())

    def insert_destination(self, table, column, func_key_id, destination_id):
        destination_query = text("""
        INSERT INTO func_key_dest_{table} (func_key_id, {column})
        VALUES (:func_key_id, :destination_id)
        """.format(table=table, column=column))

        self.connection.execute(destination_query,
                                {
                                "func_key_id": func_key_id,
                                "destination_id": destination_id})

    def insert_conference(self, name='myconf', number='2000', context='default'):
        conf_query = text("""
        INSERT INTO meetmefeatures

        (meetmeid,
        name,
        confno,
        context,
        user_announcejoinleave,
        description)

        VALUES

        (:meetmeid,
        :name,
        :confno,
        :context,
        :user_announcejoinleave,
        :description)

        RETURNING id
        """)

        conference_id = (self.connection
                         .execute(conf_query,
                                  {"meetmeid": 1234,
                                  "name": name,
                                  "confno": number,
                                  "context": context,
                                  "user_announcejoinleave": 'no',
                                  "description": ''})
                         .scalar())

        self.insert_extension(number, context, 'meetme', conference_id)

        func_key_id = self.insert_func_key('speeddial', 'conference')
        self.insert_destination('conference', 'conference_id', func_key_id, conference_id)

        return conference_id

    def insert_extension(self, exten, context, type_, typeval):
        exten_query = text("""
        INSERT INTO extensions (context, exten, type, typeval)
        VALUES (:context, :exten, :type, :typeval)
        RETURNING id
        """)

        return (self.connection
                .execute(exten_query,
                         {"context": context,
                         "exten": exten,
                         "type": type_,
                         "typeval": str(typeval)})
                .scalar())

    def insert_group(self, name='mygroup', number='1234', context='default'):
        query = text("""
        INSERT INTO groupfeatures (name, number, context, mediaserverid)
        VALUES (:name, :number, :context, (SELECT id FROM mediaserver LIMIT 1))
        RETURNING id
        """)

        group_id = (self.connection
                    .execute(query,
                             {"name": name,
                             "number": number,
                             "context": context})
                    .scalar())

        self.insert_extension(number, context, 'group', group_id)

        func_key_id = self.insert_func_key('speeddial', 'group')
        self.insert_destination('group', 'group_id', func_key_id, group_id)

        return group_id

    def insert_agent(self, number='1000', context='default', numgroup=1):
        query = text("""
        INSERT INTO agentfeatures
        (numgroup, number, passwd, context, language, description)
        VALUES (
            :numgroup,
            :number,
            '',
            :context,
            '',
            ''
        )
        RETURNING id
        """)

        func_key_query = text("""
        INSERT INTO func_key_dest_agent (func_key_id, agent_id, extension_id)
        VALUES (
        :func_key_id,
        :agent_id,
        (SELECT id FROM extensions WHERE type = 'extenfeatures' AND typeval = :typeval)
        )
        """)

        agent_id = (self.connection
                    .execute(query,
                             {"number": number,
                             "context": context,
                             "numgroup": numgroup})
                    .scalar())

        for typeval in ('agentstaticlogin', 'agentstaticlogoff', 'agentstaticlogtoggle'):
            func_key_id = self.insert_func_key('speeddial', 'agent')
            self.connection.execute(func_key_query,
                                    {
                                    "func_key_id": func_key_id,
                                    "agent_id": agent_id,
                                    "typeval": typeval})

        return agent_id

    def get_user_by_id(self, user_id):
        query = text("""SELECT * FROM userfeatures where id = :user_id""")
        res = (self.connection
               .execute(query, {"user_id": user_id})
               .first())
        return res

    def get_agent_id_by_user_id(self, user_id):
        query = text("""SELECT agentid FROM userfeatures where id = :user_id""")
        res = (self.connection
               .execute(query, {"user_id":user_id })
               .scalar())
        return res

    def get_agent_func_key(self, agent_id, typeval):
        query = text("""
        SELECT func_key_id
        FROM func_key_dest_agent
        WHERE
          agent_id = :agent_id
          AND
          extension_id = (SELECT id FROM extensions WHERE type = 'extenfeatures' AND typeval = :typeval)
        """)
        res = (self.connection
               .execute(query,
                        {"agent_id": agent_id,
                        "typeval": typeval})
               .scalar())
        return res

    def delete_agent_func_keys(self, agent_id):
        del_func_key_dest_agent_query = text("""
        DELETE FROM func_key_dest_agent
        WHERE
          func_key_id = :func_key_id
          AND
          agent_id = :agent_id
          AND
          extension_id = (SELECT id FROM extensions WHERE type = 'extenfeatures' AND typeval = :typeval)
        """)

        del_func_key = text("DELETE FROM func_key WHERE id = :func_key_id")

        for typeval in ('agentstaticlogin', 'agentstaticlogoff', 'agentstaticlogtoggle'):
            func_key_id = self.get_agent_func_key(agent_id, typeval)
            self.connection.execute(del_func_key_dest_agent_query,
                                    {
                                    "func_key_id": func_key_id,
                                    "agent_id": agent_id,
                                    "typeval": typeval})
            self.connection.execute(del_func_key,
                                    {"func_key_id": func_key_id})

    def delete_agent(self, agent_id):
        self.delete_agent_func_keys(agent_id)

        query = text("DELETE FROM agentfeatures WHERE id = :agent_id")
        self.connection.execute(query, {"agent_id": agent_id})

    def associate_user_agent(self, user_id, agent_id):
        query = text("UPDATE userfeatures SET agentid = :agent_id WHERE id = :user_id")
        self.connection.execute(query, {"user_id": user_id, "agent_id": agent_id})

    def associate_user_label(self, user_id, label_id):
        query = text("INSERT INTO userlabels (user_id, label_id) VALUES (:user_id, :label_id)")
        self.connection.execute(query, {"user_id": user_id, "label_id": label_id})

    def insert_paging(self, number='1234'):
        query = text("""
        INSERT INTO paging (number, timeout)
        VALUES (:number, :timeout)
        RETURNING id
        """)

        paging_id = (self.connection
                     .execute(query,
                              {"number": number,
                              "timeout": 30})
                     .scalar())

        func_key_id = self.insert_func_key('speeddial', 'paging')
        self.insert_destination('paging', 'paging_id', func_key_id, paging_id)

        return paging_id

    def insert_callfilter(self, name='bsfilter', type_='bosssecretary', bosssecretary='secretary-simult'):
        query = text("""
        INSERT INTO callfilter (entity_id, name, type, bosssecretary, description)
        VALUES (
        (SELECT id FROM entity LIMIT 1),
        :name,
        :type,
        :bosssecretary,
        '')
        RETURNING id
        """)

        return (self.connection
                .execute(query,
                         {"name": name,
                         "type": type_,
                         "bosssecretary": bosssecretary})
                .scalar())

    def insert_filter_member(self, callfilter_id, member_id, bstype='secretary'):
        query = text("""
        INSERT INTO callfiltermember (callfilterid, type, typeval, bstype)
        VALUES (:callfilterid, :type, :typeval, :bstype)
        RETURNING id
        """)

        filter_member_id = (self.connection
                            .execute(query,
                                     {"callfilterid": callfilter_id,
                                     "type": 'user',
                                     "typeval": str(member_id),
                                     "bstype": bstype})
                            .scalar())

        func_key_id = self.insert_func_key('speeddial', 'bsfilter')
        self.insert_destination('bsfilter', 'filtermember_id', func_key_id, filter_member_id)

        return filter_member_id

    def insert_context(self, **parameters):
        parameters.setdefault('displayname', parameters['name'])
        parameters.setdefault('description', '')
        parameters.setdefault('commented', 0)
        query = text("""
                     INSERT INTO context(name, displayname, contexttype, description, commented, entity)
                     VALUES (
                                :name, :displayname, :contexttype, :description, :commented,
                                (SELECT id FROM entity LIMIT 1)
                            )
                     """)

        self.connection.execute(query, parameters)

        return parameters['name']

    def insert_context_range(self, context, type_, start, end, didlength=0):
        query = text("""
                     INSERT INTO contextnumbers(context, type, numberbeg, numberend, didlength)
                     VALUES (:context, :type, :numberbeg, :numberend, :didlength)
                     """)

        self.connection.execute(query, {"context": context, "type": type_, "numberbeg": start, "numberend": end, "didlength": didlength})

    def delete_context(self, name):
        query = text("DELETE FROM context WHERE name = :name")
        self.connection.execute(query, {"name": name})

    def insert_entity(self, name):
        query = text("""
        INSERT INTO entity (name, description)
        VALUES (:name, '')
        RETURNING id
        """)

        entity_id = (self.connection
                     .execute(query,
                              {"name": name})
                     .scalar())

        return entity_id

    def delete_entity(self, entity_id):
        query = text("DELETE FROM entity WHERE id = :id")
        self.connection.execute(query, {"id": entity_id})

    def get_entities(self):
        query = text("SELECT * FROM entity")
        return self.connection.execute(query)

    def associate_line_device(self, line_id, device_id):
        query = text("UPDATE linefeatures SET device = :device_id WHERE id = :line_id")
        self.connection.execute(query, {"device_id": device_id, "line_id": line_id})

    def dissociate_line_device(self, line_id, device_id):
        query = text("UPDATE linefeatures SET device = NULL WHERE id = :line_id")
        self.connection.execute(query, {"device_id": device_id, "line_id": line_id})

    def insert_switchboard(self, queue_id):
        query = text("""INSERT INTO stat_switchboard_queue(time, end_type, wait_time, queue_id)
                        VALUES ('2016-02-29 00:00:00', 'abandoned', 15.2, :queue_id)
                        RETURNING id""")
        return self.connection.execute(query, {"queue_id": queue_id}).scalar()

    def find_queue(self, queue_name):
        query = text("""SELECT id from queuefeatures
                        WHERE name = :name""")
        return self.connection.execute(query, {"name": queue_name}).first()

    def insert_switchboard_stat(self, time, end_type, wait_time, queue_id):
        query = text("""INSERT INTO stat_switchboard_queue(time, end_type, wait_time, queue_id)
                        VALUES (:time, :end_type, :wait_time, :queue_id)
                        RETURNING id""")
        return self.connection.execute(query,
                                       {"time": time,
                                       "end_type": end_type,
                                       "wait_time": wait_time,
                                       "queue_id": queue_id}).scalar()

    def delete_switchboard_stat(self, stat_id):
        query = text("""DELETE from stat_switchboard_queue
                        WHERE id = :id""")
        self.connection.execute(query, {"id": stat_id})

    def line_has_sccp_device(self, line_id, sccp_device):
        query = text("""SELECT COUNT(*)
                     FROM linefeatures
                        INNER JOIN sccpline
                            ON linefeatures.protocol = 'sccp'
                            AND linefeatures.protocolid = sccpline.id
                            INNER JOIN sccpdevice ON sccpdevice.line = linefeatures.number
                     WHERE
                        linefeatures.id = :line_id
                        AND sccpdevice.device = :sccp_device
                     """)

        count = (self.connection
                 .execute(query,
                          {
                          "line_id": line_id,
                          "sccp_device":sccp_device})
                 .scalar())

        return count > 0

    def admin_has_password(self, password):
        query = text("""SELECT COUNT(*)
                     FROM "user"
                     WHERE
                        login = 'root'
                        AND passwd = :password
                     """)
        count = (self.connection
                 .execute(query,
                         {"password": password})
                 .scalar())

        return count > 0

    def autoprov_is_configured(self):
        query = text("""SELECT COUNT(*)
                     FROM staticsip
                     WHERE
                        category = 'general'
                        AND filename = 'sip.conf'
                        AND var_name = 'autocreate_prefix'
                     """)
        count = (self.connection
                 .execute(query)
                 .scalar())

        return count > 0

    def entity_has_name_displayname(self, name, displayname):
        query = text("""SELECT COUNT(*)
                     FROM entity
                     WHERE
                        name = :name
                        AND displayname = :displayname
                     """)
        count = (self.connection
                 .execute(query,
                          {"name": name,
                          "displayname": displayname})
                 .scalar())

        return count > 0

    def sip_has_language(self, language):
        query = text("""SELECT COUNT(*)
                     FROM staticsip
                     WHERE
                        var_name = 'language'
                        AND var_val = :language
                     """)
        count = (self.connection
                 .execute(query,
                          {"language": language})
                 .scalar())

        return count > 0

    def iax_has_language(self, language):
        query = text("""SELECT COUNT(*)
                     FROM staticiax
                     WHERE
                        var_name = 'language'
                        AND var_val = :language
                     """)
        count = (self.connection
                 .execute(query,
                          {"language": language})
                 .scalar())

        return count > 0

    def sccp_has_language(self, language):
        query = text("""SELECT COUNT(*)
                     FROM sccpgeneralsettings
                     WHERE
                        option_name = 'language'
                        AND option_value = :language
                     """)
        count = (self.connection
                 .execute(query,
                          {"language": language})
                 .scalar())

        return count > 0

    def general_has_timezone(self, timezone):
        query = text("""SELECT COUNT(*)
                     FROM general
                     WHERE
                        timezone = :timezone
                     """)
        count = (self.connection
                 .execute(query,
                          {"timezone": timezone})
                 .scalar())

        return count > 0

    def resolvconf_is_configured(self, hostname, domain, nameservers):
        query = text("""SELECT COUNT(*)
                     FROM resolvconf
                     WHERE
                        hostname = :hostname
                        AND domain = :domain
                        AND search = :domain
                        AND nameserver1 = :nameserver1
                        AND nameserver2 = :nameserver2
                     """)

        count = (self.connection
                 .execute(query,
                          {
                          "hostname": hostname,
                          "domain": domain,
                          "nameserver1": nameservers[0],
                          "nameserver2": nameservers[1]})
                 .scalar())

        return count > 0

    def netiface_is_configured(self, address, gateway):
        # Note that interface and netmask are not tested
        query = text("""SELECT COUNT(*)
                     FROM netiface
                     WHERE
                        hwtypeid = 1
                        AND type = 'iface'
                        AND family = 'inet'
                        AND method = 'static'
                        AND address = :address
                        AND broadcast = ''
                        AND gateway = :gateway
                        AND mtu = 1500
                        AND options = ''
                     """)

        count = (self.connection
                 .execute(query,
                          {"address": address,
                          "gateway": gateway})
                 .scalar())

        return count > 0

    def context_has_internal(self, display_name, number_start, number_end):
        query = text("""SELECT COUNT(*)
                     FROM context
                     INNER JOIN contextnumbers
                        ON context.name = contextnumbers.context
                     WHERE
                        context.name = 'default'
                        AND context.displayname = :display_name
                        AND context.contexttype = 'internal'
                        AND contextnumbers.type = 'user'
                        AND contextnumbers.numberbeg = :number_start
                        AND contextnumbers.numberend = :number_end
                        AND contextnumbers.didlength = 0
                     """)

        count = (self.connection
                 .execute(query,
                          {
                          "display_name": display_name,
                          "number_start": number_start,
                          "number_end": number_end})
                 .scalar())

        return count > 0

    def context_has_incall(self, display_name=None, number_start=None, number_end=None, did_length=None):
        if number_start is None and number_end is None:
            query = text("""SELECT COUNT(*)
                         FROM context
                         WHERE
                            context.name = 'from-extern'
                            AND context.displayname = :display_name
                            AND context.contexttype = 'incall'
                         """)

            count = (self.connection
                     .execute(query,
                              {"display_name": display_name})
                     .scalar())
        else:
            query = text("""SELECT COUNT(*)
                         FROM context
                         INNER JOIN contextnumbers
                            ON context.name = contextnumbers.context
                         WHERE
                            context.name = 'from-extern'
                            AND context.displayname = :display_name
                            AND context.contexttype = 'incall'
                            AND contextnumbers.type = 'incall'
                            AND contextnumbers.numberbeg = :number_start
                            AND contextnumbers.numberend = :number_end
                            AND contextnumbers.didlength = :did_length
                         """)

            count = (self.connection
                     .execute(query,
                              {
                              "display_name": display_name,
                              "number_start": number_start,
                              "number_end": number_end,
                              "did_length": did_length})
                     .scalar())

        return count > 0

    def context_has_outcall(self, display_name):
        query = text("""SELECT COUNT(*)
                     FROM context
                     WHERE
                        context.name = 'to-extern'
                        AND context.displayname = :display_name
                        AND context.contexttype = 'outcall'
                     """)

        count = (self.connection
                 .execute(query,
                          {"display_name":display_name})
                 .scalar())

        return count > 0

    def context_has_switchboard(self):
        query = text("""SELECT COUNT(*)
                     FROM context
                     WHERE
                        context.name = '__switchboard_directory'
                        AND context.displayname = 'Switchboard'
                        AND context.contexttype = 'others'
                     """)

        count = (self.connection
                 .execute(query)
                 .scalar())

        return count > 0

    def internal_context_include_outcall_context(self):
        query = text("""SELECT COUNT(*)
                     FROM contextinclude
                     WHERE
                        context = 'default'
                        AND include = 'to-extern'
                        AND priority = 0
                     """)

        count = (self.connection
                 .execute(query)
                 .scalar())

        return count > 0

    def rightcall_has_entry(self, name):
        query = text("""SELECT COUNT(*)
                     FROM rightcall
                     WHERE
                        name = :name
                     """)

        count = (self.connection
                 .execute(query,
                          {"name":name})
                 .scalar())

        return count > 0

    def sip_allows_codecs(self, codecs):
        query = text("""SELECT COUNT(*)
                     FROM staticsip
                     WHERE
                        var_name = 'allow'
                        AND var_val = :codecs
                     """)

        count = (self.connection
                 .execute(query,
                          {"codecs": codecs})
                 .scalar())

        return count > 0

    def xuc_func_key_template_is_created(self):
        query = text("""SELECT COUNT(*)
                     FROM func_key_template
                     WHERE
                        name = 'xuc technical'
                        AND private = True
                     """)
        count = (self.connection
                 .execute(query)
                 .scalar())

        return count > 0

    def xuc_user_is_created(self):
        query = text("""SELECT COUNT(*)
                     FROM userfeatures
                     WHERE
                        firstname = 'xuc'
                        AND agentid = 0
                        AND entityid = 1
                        AND callerid = 'xuc'
                        AND lastname = 'technical'
                        AND enableclient=1
                        AND loginclient='xuc'
                        AND length(passwdclient)=32
                     """)
        count = (self.connection
                 .execute(query)
                 .scalar())

        return count > 0

    def insert_label(self, display_name='a label', description='a description'):
        queue_query = text("""
        INSERT INTO labels (display_name, description)
        VALUES (:display_name, :description)
        RETURNING id
        """)

        label_id = (self.connection
                    .execute(queue_query,
                             {"display_name": display_name,
                             "description":description})
                    .scalar())

        return label_id

    def delete_label(self, label_id):
        query = text("DELETE FROM labels WHERE id = :label_id")
        self.connection.execute(query, {"label_id":label_id})


def create_helper():
    user = os.environ.get('DB_USER', 'asterisk')
    password = os.environ.get('DB_PASSWORD', 'proformatique')
    host = os.environ.get('DB_HOST', 'localhost')
    port = os.environ.get('DB_PORT', 15432)
    db = os.environ.get('DB_NAME', 'asterisk')

    return DbHelper.build(user, password, host, port, db)
