import re
import socket
import json
import base64
from multiprocessing import Process
import mimetypes
mimetypes.init()
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (Mail, Attachment, FileContent, FileName, FileType, Disposition)
from twilio.rest import Client
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from dl2050utils.core import *

# ####################################################################################################
# com Module
#
# Send email notifications with Sendgrid and phone text notifications with Twillio
#
# Main parts:
#     1) read_address_books: very basic address book management
#     2) Notify: class to manage all communication features
#     3) send_otp: send OTP codes by email or phone (helper function for auth Module)
#
# ####################################################################################################

# ####################################################################################################
# Address book
# ####################################################################################################

default_address_book = {
    'jneto': {'email': 'joao.filipe.neto@gmail.com', 'phone': '+351966221506'},
    'pneto': {'email': 'pneto.98@gmail.com', 'phone': '+351910528813'},
    'jranito': {'email': 'joao.vasco.ranito@gmail.com', 'phone': '+351966221505'},
    'rassuncao': {'email': 'rui.assuncao@ndrive.com ', 'phone': '+351933339307'}
}

def read_address_book():
    try:
        with open('./.addressbook') as f: d = json.loads(f.read())
    except IOError:
        return None
    return d

# ####################################################################################################
# Notify
# ####################################################################################################

def send_mail(api_key, to, subject=None, msg=None, html=None, files=[]):
    """
        Send email by SendGrid service
    """
    to = listify(to)
    try:
        msg = msg or '   '
        html2 = html or msg
        subject = subject or '  '
        msg = Mail(from_email='ops@dl2050.com', to_emails=to, subject=subject, html_content=html2)
        if len(files):
            attachments = []
            for file in files:
                with open(str(file), 'rb') as f: data=f.read()
                data = base64.b64encode(data).decode()
                attachedFile = Attachment(
                    FileContent(data),
                    FileName(file.name),
                    FileType(mimetypes.types_map[file.suffix]),
                    Disposition('attachment')
                )
                attachments.append(attachedFile)
            msg.attachment = attachments
        sg = SendGridAPIClient(api_key)
        res = sg.send(msg)
    except Exception as e:
        return f'SendGrid ERROR: {str(e)}'
    if res.status_code!=202: return f'SendGrid ERROR: status code={res.status_code}'
    return None

def send_mail_async(api_key, to, subject=None, msg=None, html=None, files=[]):
    p = Process(target=send_mail, args=(api_key, to, subject, msg, html, files), daemon=True)
    p.start()

def send_sms(account_sid, auth_token, service_name, to, msg):
    to = listify(to)
    try:
        client = Client(account_sid, auth_token)
        for e in to:
            message = client.messages.create(body=msg, from_=service_name, to=e)
        return None
    except Exception as e:
        return f'Twilio Exception: {str(e)}'

def send_slack(token, channel, msg, file=None):
    if not channel: return 'Slack: missing channel'
    try:
        client = WebClient(token=token)
        if file is None:
            response = client.chat_postMessage(channel=channel, text=msg)
        else:
            response = client.files_upload(channel=channel, file=file, initial_comment=msg)
        if response['ok']:
            return None
        else:
            return f'Slack send error'
    except SlackApiError as exc:
        return f'Slack send exception: {exc.response["error"]}'

class Notify():
    def __init__(self, cfg, address_book=None):
        if cfg is None: raise Exception('Notify: cfg not defined')
        self.address_book = address_book if address_book is not None else default_address_book
        self.email_key = oget(cfg,['email','sendgrid_api_key'])
        self.sms_id = oget(cfg,['aws','aws_access_key_id'])
        self.sms_passwd = oget(cfg,['aws','aws_secret_access_key'])
        self.slack_token = oget(cfg,['slack','token'])
        
    def __call__(self, how, to=None, subject=None, channel=None, msg=None, html=None, files=[]):
        if how == 'email':
            return self.send_mail(to, subject=subject, msg=msg, html=html, files=files)
        elif how=='sms':
            return send_sms(self.sms_id, self.sms_passwd, to, msg)
        elif how=='slack':
            return self.send_slack(channel, msg)
        else:
            raise Exception('Invalid method, options are email, sms or slack')
    
    def send_mail_async(self, to, subject=None, msg=None, html=None, files=[]):
        if self.email_key is None: return 'email credentials not defined'
        to = listify(to)
        if not len(to): return 'No destination addresses'
        to = [self.address_book[e]['email'] if e in self.address_book else e for e in to]
        if self.email_key is None: raise Exception('Notify: email key not defined')
        send_mail_async(self.email_key, to, subject=subject, msg=msg, html=html, files=files)
    
    def send_mail(self, to, subject=None, msg=None, html=None, files=[]):
        if self.email_key is None: return 'email credentials not defined'
        to = listify(to)
        if not len(to): return 'No destination addresses'
        to = [self.address_book[e]['email'] if e in self.address_book else e for e in to]
        if self.email_key is None: raise Exception('Notify: email key not defined')
        return send_mail(self.email_key, to, subject=subject, msg=msg, html=html, files=files)

    def send_sms(self, to, msg):
        if self.sms_id is None or self.sms_passwd is None: return 'sms credentials not defined'
        to = listify(to)
        if not len(to): return 'No destination addresses'
        if self.sms_id is None: raise Exception('Notify: sms_id key not defined')
        if self.sms_passwd is None: raise('Notify: sms_passwd key not defined')
        return send_sms(self.sms_id, self.sms_passwd, to, msg)
    
    def send_slack(self, channel, msg):
        if self.slack_token is None: raise Exception('Notify: slack_token key not defined')
        return send_slack(self.slack_token, channel, msg)

# ####################################################################################################
# Send OTP
# ####################################################################################################

EMAIL_TEMPLATE = \
"""
<html>
<head>
    <link href="https://fonts.googleapis.com/css?family=Muli::100,200,300,400,500,600,700,800" rel="stylesheet">
</head>
    <body style="position: relative; float: left; width: 100%; height: 100%;  text-align: center; font-family: 'Muli', sans-serif;">
        <h2 style="float: left; width: 100%; margin: 40px 0px 10px 0px; font-size: 16px; text-align: center; color: #555555;">{msg}</h2>
        <h2 style="float: left; width: 100%; margin: 0px 0px 40px 0px; font-size: 24px; text-align: center; color: #61C0DF; font-weight: bold;">{otp}</h2>
    </body>
</html>
"""

def send_otp_by_email(notify, product, email, otp):
    try:
        subject = f'{product} OTP'
        msg = f'{product} OTP: '
        html = EMAIL_TEMPLATE
        html = re.sub(r'{msg}', msg, html)
        html = re.sub(r'{otp}', f'{otp}', html)
        notify.send_mail_async(email, subject=subject, html=html)
    except Exception as e:
        return str(e)
    return None

def send_otp_by_phone(notify, product, phone, otp):
    msg = f'{product} OTP: {otp}'
    try:
        notify.send_sms(phone, msg)
    except Exception as e:
        return str(e)
    return None

def send_otp(notify, mode, product, email, phone, otp):
    if mode=='phone': return send_otp_by_phone(notify, product, phone, otp)
    return send_otp_by_email(notify, product, email, otp)

# ####################################################################################################
# etc
# ####################################################################################################

def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP