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

# Copyright (C) 2011-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, see <http://www.gnu.org/licenses/>

from __future__ import print_function
from builtins import str
from builtins import object
import os
import sys
import hashlib
import base64

from xivo_dao.helpers.db_utils import session_scope

from xivo_dao import asterisk_conf_dao, line_dao, agent_dao
from xivo_dao.resources.endpoint_sip import dao as sip_dao
from xivo_dao.resources.infos import dao as infos_dao

from xivo_confgen.generators.sip2pjsip.astconfigparser import MultiOrderedConfigStorage
from xivo_confgen.generators.sip2pjsip.sip_to_pjsip import convert

def key_found_and_equal(key, value, options):
    return [key, value] in ([option_name, option_value] for option_name, option_value in options)

def key_found(key, options):
    return key in (option_name for option_name, _ in options)



CC_POLICY_ENABLED = 'generic'
CC_POLICY_DISABLED = 'never'
CC_OFFER_TIMER = 30
CC_RECALL_TIMER = 20
CCBS_AVAILABLE_TIMER = 900
CCNR_AVAILABLE_TIMER = 900
FILE_VERSION = '/usr/share/xivo/XIVO-VERSION'


class PjsipConf(object):
    WEBRTC_KEY = 'webrtc'
    WEBRTC_TRANSPORT_UDP = ['transport', 'udp']
    WEBRTC_TRANSPORT_WS = ['transport', 'ws']

    EXCLUDE_OPTIONS = ('name',
                       'protocol',
                       'category',
                       'disallow',
                       'regseconds',
                       'lastms',
                       'name',
                       'fullcontact',
                       'ipaddr')




    TRUNK_EXCLUDE_OPTIONS = [
        'id',
        'name',
        'commented',
        'protocol',
        'category',
        'disallow'
    ]

# if duplicate keys are needed (e.g.setvar) transform dicts to list of 
# (key, value) pairs and use section.insert_pair_list in code

    MDS_USER_OPTIONS = {
        "call-limit": "0",
        "subscribemwi": "no",
        "amaflags": "default",
        "transport": "udp",
        "regseconds": "0",
        "type": "user",
        "context": "xds-from-mds"
    }

    MDS_PEER_OPTIONS = {
        "call-limit": "0",
        "subscribemwi": "no",
        "amaflags": "default",
        "transport": "udp",
        "regseconds": "0",
        "type": "peer",
        "port": "5060",
        "context": "default",
        "qualify": "yes"
    }


    WEBRTC_OPTIONS = [
        ('avpf', 'yes'),
        ('call-limit', '2'),
        ('encryption', 'yes'),
        ('force_avp', 'yes'),
        ('icesupport', 'yes'),
        ('dtlsenable', 'yes'),
        ('dtlsverify', 'no'),
        ('dtlscertfile', '/etc/asterisk/keys/asterisk.pem'),
        ('dtlsprivatekey', '/etc/asterisk/keys/asterisk.pem'),
        ('dtlssetup', 'actpass'),
        ('sendrpid', 'no'),
        ('rtptimeout', '20'),
        ('rtcp_mux', 'yes'),
        ('nat', 'force_rport'),
        ('cust_max_contacts', '4'),
    ]
    
    WEBRTC_CODEC_DEFAULT = [
        ('disallow', 'all'),
        ('allow', 'opus,alaw,vp8')
    ]

    NONCONFIGURABLE_OPTIONS = [
        ('trust_id_outbound', 'yes')
    ]

    DUMMY_ENDPOINT_OPTIONS = [
        ('type', 'endpoint')
    ]

    def __init__(self, nova_compatibility):
        self._nova_compatibility = nova_compatibility
        self.systemname = os.getenv('MDS_NAME', 'default')
        self.sip = MultiOrderedConfigStorage()
        self._stun_address_defined = self._is_stun_address_defined()
        
    def generate(self, output):
        with session_scope():
            self._generate(output)

    def _generate(self, output):
        self.add_section_general()
        self.add_section_authentication()
        self.add_trunks()
        self.add_mds_trunks()

        data_ccss = asterisk_conf_dao.find_extenfeatures_settings(['cctoggle'])
        ccss_options = self._ccss_options(data_ccss)
        self.add_users(ccss_options)
        
        pjsip, non_mapings = convert(self.sip)
        
        for line in pjsip.generate():
            print(line, file=output)
            if line == '[global]':
                print('default_outbound_endpoint = dummy_endpoint', file=output)
                # Changing the endpoint identifiers order to first check the SIP From: username
                # This should copy the behaviour of chan_sip
                print("endpoint_identifier_order=username,ip,anonymous", file=output)

        dummy_endpoint = "\n[dummy_endpoint]\ntype = endpoint\n"
        print(dummy_endpoint, file=output)
    
    
    def add_section_general(self):
        data_general = asterisk_conf_dao.find_sip_general_settings(self.systemname)
        sect = self.sip.add_section('general', mdicts=self.sip.defaults())
        for item in data_general:
            if item['var_val'] is None:
                continue

            if item['var_name'] == "stunaddr":
                continue

            if item['var_name'] in ('register', 'mwi'):
                sect[item['var_name']] = item['var_val']

            elif item['var_name'] == 'useragent' and item['var_val'] == 'XIVO_VERSION':
                ua_version = self._get_user_agent_version()
                sect[item['var_name']] = ua_version

            elif item['var_name'] == 'prematuremedia':
                var_val = 'yes' if item['var_val'] == 'no' else 'no'
                sect[item['var_name']] = var_val

            elif item['var_name'] not in ['allow', 'disallow']:
                sect[item['var_name']] = item['var_val']

            elif item['var_name'] == 'allow':
                sect['disallow'] = 'all'
                sect['allow'] = item['var_val']

        sect.insert_pair_list(self.NONCONFIGURABLE_OPTIONS)


    def add_section_authentication(self):
        data_auth = asterisk_conf_dao.find_sip_authentication_settings()
        sect = self.sip.add_section('authentication')
        for auth in data_auth:
            mode = '#' if auth['secretmode'] == 'md5' else ':'
            sect["auth"] = "%s%s%s@%s" % (auth['user'], mode, auth['secret'], auth['realm'])



    def add_trunks(self):
        trunks = sip_dao.find_all_by(commented=0, category='trunk')
        
        for trunk in trunks:
            options = trunk.all_options(exclude=self.TRUNK_EXCLUDE_OPTIONS)
            sect = self.sip.add_section(trunk.name, 'general')
            if asterisk_conf_dao.is_sip_trunk_on_current_mds(trunk.name, self.systemname):
                if self.codec_customized_on_line(options):
                    sect['disallow'] = 'all'
                sect.insert_pair_list(options)
    
    def add_mds_trunks(self):
        mediaservers = asterisk_conf_dao.find_all_mediaservers()        
        for mds in mediaservers:
            if mds.name != self.systemname:
                secret = base64.urlsafe_b64encode(hashlib.sha256(bytearray(infos_dao.get().uuid, 'utf-8')).digest()).decode()

                sect_user = self.sip.add_section("from-{}".format(mds.name), 'general')
                sect_user['secret'] = secret
                sect_user['remotesecret'] = secret
                sect_user.insert_dict(self.MDS_USER_OPTIONS)

                sect_peer = self.sip.add_section("to-{}".format(mds.name))
                sect_peer['secret'] = secret
                sect_peer['remotesecret'] = secret
                sect_peer['username'] = "from-{}".format(self.systemname)
                sect_peer['fromuser'] = "from-{}".format(self.systemname)
                sect_peer['host'] = mds.voip_ip
                sect_peer.insert_dict(self.MDS_PEER_OPTIONS)
            
    
    def add_users(self, ccss_options):
        for row in asterisk_conf_dao.find_sip_user_settings():
            if row.configregistrar != self.systemname:
                continue
            
            self._add_user_row(row, ccss_options)
            
    def _add_user_row(self, row, ccss_options):
        usersip_name = row.UserSIP.name
        sect = self.sip.add_section(usersip_name, 'general')

        usersip_options = [('setvar', 'CHANNEL(hangup_handler_push)=hangup_handlers,s,1')]
        
        usersip_options += self._user_options(row)
        usersip_options += ccss_options.items()
        
        options = row.UserSIP.all_options(self.EXCLUDE_OPTIONS)
        if self.codec_customized_on_line(options):
            usersip_options.append(('disallow', 'all'),)

        for k, v in options:
            if not k.startswith(self.WEBRTC_KEY):
                usersip_options.append((k, v),)

        if key_found_and_equal(self.WEBRTC_KEY, 'yes', options):
            if not line_dao.device_exists_for_sip_id(row.UserSIP.id):
                usersip_options += self.WEBRTC_OPTIONS
                if self.codec_customized_on_line(options):
                    usersip_options += self.WEBRTC_CODEC_DEFAULT
                
                self._add_transport_type(usersip_options)

        sect.insert_pair_list(usersip_options)

        # Generate additional peer section with webrtc option for UA line
        if key_found_and_equal(self.WEBRTC_KEY, 'ua', options):
            self._add_transport_type(usersip_options)
            
            sect_ua = self.sip.add_section(usersip_name + '_w', 'general')
            sect_ua.insert_pair_list(usersip_options)
            sect_ua.insert_pair_list(self.WEBRTC_OPTIONS)
            if not self.codec_customized_on_line(options):
                sect_ua.insert_pair_list(self.WEBRTC_CODEC_DEFAULT)


    def _user_options(self, row):
        ret = []
        if row.context:
            ret.append(('setvar', 'TRANSFER_CONTEXT={}'.format(row.context)),)
        if row.number and row.context:
            ret.append(('setvar', 'PICKUPMARK={}%{}'.format(row.number, row.context)),)
        if row.user_id:
            ret.append(('setvar', 'XIVO_USERID={}'.format(row.user_id)),)
        if row.uuid:
            ret.append(('setvar', 'XIVO_USERUUID={}'.format(row.uuid)),)
        if row.namedpickupgroup:
            ret.append(('namedpickupgroup', row.namedpickupgroup),)
        if row.namedcallgroup:
            ret.append(('namedcallgroup', row.namedcallgroup),)
        if row.mohsuggest:
            ret.append(('mohsuggest', row.mohsuggest),)
        if row.mailbox:
            ret.append(('mailbox', row.mailbox),)
        if row.UserSIP.callerid:
            ret.append(('description', row.UserSIP.callerid),)
        if self._nova_compatibility and row.number:
            ret.append(('accountcode', row.number),)
        
        return ret


    def _ccss_options(self, data_ccss):
        if data_ccss:
            ccss_info = data_ccss[0]
            if ccss_info.get('commented') == 0:
                return {
                    'cc_agent_policy': CC_POLICY_ENABLED,
                    'cc_monitor_policy': CC_POLICY_ENABLED,
                    'cc_offer_timer': CC_OFFER_TIMER,
                    'cc_recall_timer': CC_RECALL_TIMER,
                    'ccbs_available_timer': CCBS_AVAILABLE_TIMER,
                    'ccnr_available_timer': CCNR_AVAILABLE_TIMER,
                }

        return {
            'cc_agent_policy': CC_POLICY_DISABLED,
            'cc_monitor_policy': CC_POLICY_DISABLED,
        }

    def _get_user_agent_version(self):
        xivo_version = self._get_xivo_version()
        if xivo_version != '':
            ua_version = 'XiVO PBX %s' % xivo_version
        else:
            ua_version = 'XiVO PBX'

        return ua_version

    def _get_xivo_version(self):
        try:
            with open(FILE_VERSION, 'r') as f:
                xivo_version = f.readline().rstrip()
        except:
            xivo_version = ''

        return xivo_version

    def _add_transport_type(self, usersip_options):
        if self._stun_address_defined:
            usersip_options.append(self.WEBRTC_TRANSPORT_UDP)
        else:
            usersip_options.append(self.WEBRTC_TRANSPORT_WS)

    def _is_stun_address_defined(self):
        data_general = asterisk_conf_dao.find_sip_general_settings(self.systemname)
        stun = next((item for item in data_general if item["var_name"] == "stunaddr" and item["var_val"] is not None), None)
        return bool(stun)

    def codec_customized_on_line(self, options):
        return key_found('allow', options)
    
    