#!/usr/bin/python3

# Copyright (C) 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/>

# 2016-12-13 v1.4 Julien Klingenmeyer <jklingenmeyer@avencall.com>
# This program uses XiVO API and was tested with XiVO Solutions 2016.03
# WARNING: Any execution with a later version may not work

# Usage:  This script associates an *existing* line with an *existing* device.
#         Argument must be a CSV file with the phone number of the line and
#         the MAC address of the device separated by a comma.
#         Command: python edit_users-add-device-by-mac.py csv_file.csv
# Prereq: - a web services user created on XiVO
#         - lines and devices to associate already created on XiVO
# Output: phone_nb,mac_addr,status on stdout

from __future__ import print_function
from builtins import zip
from builtins import str
from builtins import object
import argparse
import csv
import json
import requests
import ssl
import sys

### PARAMETERS ###
# Please edit the following parameters
# according to your current configuration
XIVO_HOST="10.10.105.1"
WS_USERNAME="ws_user"
WS_PASSWORD="ws_pass"

### ADDITIONAL VARS ###
# The following parameters may have to be corrected if
# the XiVO confd API has changed in a later version
URI_LINES = '/1.1/lines'
URI_DEVICES = '/1.1/devices'
CSV_COLUMNS = ['phonenumber', 'mac']

class WebServicesResponse(object):
    def __init__(self, url, response):
        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as err:
            self.data = None
            #print "Error when executing a request"
            #print err
        self.url = url
        self.code = response.status_code
        self.data = None
        if self.code == 200 and response.headers['Content-Type'] == "application/json":
            self.data = response.json()

class WebServices(object):
    def __init__(self, remote_host, uri_suffix, ws_username, ws_password):
        self._wsr = None
        self._path = self._compute_path(remote_host, uri_suffix)
        self._uri_suffix = uri_suffix
        self._ws_username = ws_username
        self._ws_password = ws_password
        self._session = self._build_session(self._ws_username, self._ws_password)

    def _compute_path(self, remote_host, uri_suffix):
        if 'localhost' in remote_host or '127.0.0.1' in remote_host:
            prot = 'http'
            remote_port = 9487
        else:
            prot = 'https'
            remote_port = 9486

        return '%s://%s:%s%s' % (prot,remote_host,remote_port,uri_suffix)

    def _request_http(self, method, url_suffix=None, qry=None, content=None):
        if isinstance(content, dict):
            content = json.dumps(content)
        url = self._path
        if url_suffix is not None:
            url = '%s%s' % (self._path, url_suffix)
        try:
            if method == 'put':
                response = self._session.put(url)
                self._wsr = WebServicesResponse(url, response)
            elif method == 'get':
                response = self._session.get(url, data=content, params=qry)
                self._wsr = WebServicesResponse(url, response)
            elif method == 'post':
                response = self._session.post(url, data=content)
                self._wsr = WebServicesResponse(url, response)
        except requests.exceptions.RequestException as e:
                print("Error while attempting to connect to XiVO. Please check and try again. Exiting.")
                sys.exit(1)
        return self._wsr

    def _build_session(self, username, password):
        requests.packages.urllib3.disable_warnings()
        s = requests.Session()
        s.verify = False
        s.headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}     
        if username is not None or password is not None:
            s.auth = requests.auth.HTTPDigestAuth(username, password)
        return s

    def list(self):
        method = "get"
        return self._request_http(method)

    def associate(self, line_id, device_id):
        method = "put"
        url_suffix = '/%s/devices/%s' % (line_id, device_id)
        return self._request_http(method, url_suffix)

def decode_ws_response(resp):
    if resp.code == 200:
        #return json.loads(resp.data)
        return resp.data
    elif resp.code == 204:
        return "OK204"
    elif resp.code == 404:
        return "ERROR404-Not found"
    elif resp.code == 400:
        return "ERROR400-A device was maybe already associated to this user"
    elif resp.code == 401:
        return "ERROR401"
    else:
        return "ERRORUNDEF"

def _parse_args():
    parser = argparse.ArgumentParser()
    #    parser.add_argument('-u', '--username',
    #                        help='authentication username')
    #    parser.add_argument('-p', '--password',
    #                        help='authentication password')
    #    parser.add_argument('-H', '--hostname', default='localhost',
    #                        help='hostname of xivo server')
    parser.add_argument('csv_file',
                        help='csv filename')
    return parser.parse_args()

def convert_row_to_dict(row):
    return dict(list(zip(CSV_COLUMNS, [s.decode('UTF-8') for s in row])))

def edit_lines(line_ws, device_ws, association_ws, filename):
    devices = decode_ws_response(device_ws.list())
    if devices=="ERROR401":
        print('''ERROR401: Unable to connect to XiVO. Please check username and password.''')
        sys.exit(1)
    elif devices is None:
        print('''Error: no devices on this XiVO or unable to connect to XiVO.\
                \nDevices must first be created by provd before being able to associate a line to a device.''')
        sys.exit(1)
    else:
        print('Fetching devices list...')
        device_id_by_mac = dict((str(device['mac']).lower(), device['id']) for device in devices['items'])

    print('Fetching lines list...')
    lines = decode_ws_response(line_ws.list())
    if lines is None:
        print('''Error: no lines on this XiVO.\
                \nLines must first be created before being able to associate a device to a line.''')
        sys.exit(1)
    else:
        line_id_by_line_num = dict((line['caller_id_num'], line['id']) for line in lines['items'])

    print('Reading file %s...' % filename)
    with open(filename, 'rb') as fobj:
        reader = csv.reader(fobj, delimiter=',')
        for row in reader:
            row_dict = convert_row_to_dict(row)
            if row_dict['mac'] not in device_id_by_mac:
                print("%s,%s,WARN:no device with this mac address was found on XiVO" % (row_dict['phonenumber'], row_dict['mac']))
                continue
            else:
                try:
                    my_line_id = line_id_by_line_num[row_dict['phonenumber']]
                    my_device_id = device_id_by_mac[row_dict['mac']]
                    resp_assoc = decode_ws_response(association_ws.associate(my_line_id, my_device_id))
                except KeyError:
                    resp_assoc = "ERROR404-Phone number or MAC address does not exist on XiVO"
                if resp_assoc == "OK204":
                    print("%s,%s,OK" % (row_dict['phonenumber'], row_dict['mac']))
                else:
                    print("%s,%s,ERROR:%s" % (row_dict['phonenumber'], row_dict['mac'], resp_assoc))

def main():
    parsed_args = _parse_args()

    xivo = XIVO_HOST 
    username = WS_USERNAME
    password = WS_PASSWORD

    print("Connecting...")
    print(" xivo : %s " % xivo)

    line_ws = WebServices(xivo, URI_LINES, username, password)
    association_ws = WebServices(xivo, URI_LINES, username, password)
    device_ws = WebServices(xivo, URI_DEVICES, username, password)

    edit_lines(line_ws, device_ws, association_ws, parsed_args.csv_file)


if __name__ == '__main__':
    main()
