#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Pymetrick.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

"""Modulo para gestionar mail"""

try:
    from pymetrick.version import __author__, __version__, __license__, __copyright__
except ImportError:
    raise ImportError(
        'Failed to find (autogenerated) version.py '
    )

__date__ = '2014-05-21'
__credits__ = ''
__text__ = 'Gestion de funciones para Mail'
__file__ = 'mail.py'

#--- CHANGES ------------------------------------------------------------------
# 2015-05-04 v0.01 PL: - First version
# 2017-10-02 v0.43 PL: - Migrar a PYTHON 3.6

import sys
# sin bytecode
sys.dont_write_bytecode = True
import os
from os.path import basename, isfile
import re

import logging
import logging.handlers
import gc
import smtplib
import imaplib
try:
    import ssl
    from smtplib import SMTP, SMTP_SSL        # use this for standard SMTP protocol   (port 25, no encryption or port 465, uses SSL)
    from imaplib import IMAP4, IMAP4_SSL, Time2Internaldate
    IS_SSL = True
except:
    from smtplib import SMTP                  # use this for standard SMTP protocol   (port 25, no encryption or port 465, uses SSL)
    from imaplib import IMAP4,Time2Internaldate
    IS_SSL = False

import socket

# For guessing MIME type based on file name extension
import mimetypes

from optparse import OptionParser

import email
import mailbox
from email import encoders
from email.message import Message
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.audio import MIMEAudio
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.utils import COMMASPACE, formatdate

#from datetime import datetime, timedelta, tzinfo

from pymetrick.helpers import *
import time

# PY3
long = int

# Send an HTML email with an embedded image and a plain text message for
# email clients that don't want to display the HTML.

'''
DEBUG - debug message
INFO - info message
WARNING - warn message
ERROR - error message
CRITICAL - critical message
'''
LOG_LEVEL_VALUE = {'DEBUG':10,'INFO':20,'WARNING':30,'ERROR':40,'CRITICAL':50}
if str(os.environ.get('PYMETRICK_LOG_LEVEL',None)).upper() in LOG_LEVEL_VALUE:
    LOG_LEVEL = LOG_LEVEL_VALUE[str(os.environ.get('PYMETRICK_LOG_LEVEL')).upper()]
else:
    LOG_LEVEL = 30
LOG_FILENAME = '-'.join([os.path.abspath(__file__).split(os.sep)[len(os.path.abspath(__file__).split(os.sep))-1],])[:-3]
LOG = logging.getLogger(LOG_FILENAME)

if 'LD_LIBRARY_PATH' in list(os.environ.keys()):
    # CGI environment
    sys.stdout = sys.stderr
    logging.basicConfig(stream = sys.stderr, level=LOG_LEVEL, format='%(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
else:
    # not CGI environment
    logging.basicConfig(stream=sys.stderr)
    hdlr = logging.handlers.RotatingFileHandler(filename=LOG_FILENAME+'.log',mode='a', encoding='utf-8', maxBytes=1048576, backupCount=3)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
    hdlr.setFormatter(formatter)
    LOG.addHandler(hdlr)
    LOG.setLevel(LOG_LEVEL)


SMTP_DEFAULT_PORT = 25
SMTP_SSL_PORT = 465
SMTP_TLS_PORT = 587

IMAP4_PORT = 143
IMAP4_SSL_PORT = 993

def sendMail(*args,**kwargs):
    '''Enviar email,
       @Arguments:
           _sender       - str  -  enviado desde
           _to           - list -  enviar a
           _cc           - list -  enviar copias
           _bcc          - list -  enviar copias ocultas
           _subject      - str  -  asunto
           _text         - str  -  text/plain del mensaje
           _html         - str  -  text/html  del mensaje
           _user         - str  -  usuario
           _password     - str  -  password
           _smtpserver   - str  -  servidor SMTP
           _imapserver   - str  -  servidor IMAP
           _smtp_port    - str  -  puerto smtp
           _imap_port    - str  -  puerto imap
           _files        - list -  ficheros adjuntos
           _output       - str  -  el contenido del mensaje se grabara como un fichero
       @Returns 200|401
    '''
    # for standard SMTP protocol port 25, no encryption
    # for standard SMTP protocol port 465, SSL
    # for standard SMTP protocol port 587, TLS

    #Tratamiento de errores en LOG


    try:
        gc.collect()
        size = 0
        return_code = None
        return_text = ''
        valid_email = re.compile(r'^.+@.+')
        # Create the root message and fill in the from, to, and subject headers
        # keeps the textual and attachment parts separately
        msgRoot = MIMEMultipart('mixed')
        msgRoot['Subject'] = bytes2str(kwargs.get('_subject',''))
        # my email address
        msgRoot['From'] = bytes2str(kwargs.get('_sender',''))
        lstTo = list()
        
        if kwargs.get('_to',None) and valid_email.match(str(kwargs.get('_to',''))):
            # recipient's email address
            _to = kwargs.get('_to') if isinstance(kwargs.get('_to'),(list,tuple)) else bytes2str([kwargs.get('_to')]);
            msgRoot['To'] = COMMASPACE.join(_to)
            lstTo = _to
            if kwargs.get('_cc',None) and valid_email.match(str(kwargs.get('_cc',''))):
                _cc = kwargs.get('_cc') if isinstance(kwargs.get('_cc'),(list,tuple)) else bytes2str([kwargs.get('_cc')]);
                msgRoot['cc'] = COMMASPACE.join(_cc)
                lstTo += _cc
            if kwargs.get('_bcc',None) and valid_email.match(str(kwargs.get('_bcc',''))):
                _bcc = kwargs.get('_bcc') if isinstance(kwargs.get('_bcc'),(list,tuple)) else bytes2str([kwargs.get('_bcc')]);
                msgRoot['bcc'] = COMMASPACE.join(_bcc)
                lstTo += _bcc
            #msgRoot['Date'] = timeZone('Madrid').strftime( "%m/%d/%Y %H:%M" )
            msgRoot.preamble = 'This is a multi-part message in MIME format.'

            # Encapsulate the plain and HTML versions of the message body in an
            # 'alternative' part, so message agents can decide which they want to display.
            msgAlternative = MIMEMultipart('alternative')
            msgRoot.attach(msgAlternative)
            # MIMEText(_text[, _subtype[, _charset]])
            # Record the MIME types - text/plain
            msgAlternative.attach(MIMEText(kwargs.get('_text',''), _subtype='plain',_charset='utf-8'))
            # Record the MIME types - text/html.
            msgAlternative.attach(MIMEText(kwargs.get('_html',''), _subtype='html',_charset='utf-8'))

            # Ejemplo de como introducir una imagen en el texto html
            #'<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>Nifty!'

            # We reference the file in the SRC attribute by the ID
            if kwargs.get('_files',None):
                _files = kwargs['_files']
                for f in _files or []:
                    try:
                        if isfile(f):
                            size += os.path.getsize(f)
                            with open(f, "rb") as fr:
                                part = MIMEApplication(
                                    fr.read(),
                                    Name=basename(f)
                                )
                            # After the file is closed
                            part['Content-Disposition'] = 'attachment; filename="%s"' % basename(f)
                            msgRoot.attach(part)
                    except Exception as e:
                        tb = sys.exc_info()[2]
                        LOG.error('sendEmail <%s> in line %s !!!' % (repr(e),tb.tb_lineno))
                        del msgRoot
                        return 401

            # Send the email (this example assumes SMTP authentication is required)
            port = kwargs.get('_smtp_port',SMTP_DEFAULT_PORT)  # or 587
            if port==SMTP_SSL_PORT:                  # SSL
                server = SMTP_SSL(host=kwargs.get('_smtpserver',''),port=port,timeout=1000*6)
            else:
                server = SMTP(host=kwargs.get('_smtpserver',''),port=port,timeout=1000*6)
            # debuglevel
            # server.set_debuglevel(True) # show communication with the server

            # 220, b'pymetrick.com ESMTP Haraka/2.8.27 ready'
            return_code, return_text = server.connect(kwargs.get('_smtpserver',None), port)
            if return_code >= 200 and return_code <= 299 and server.ehlo()[0] == 250:
                #code = server.ehlo()[0]
                maximum_message_size_in_bytes = int( server.esmtp_features['size'] )
                LOG.debug('********** TAMAÑO MAXIMO ****************')
                LOG.debug(maximum_message_size_in_bytes)
                LOG.debug(f'''attachment size {size}''')
                if IS_SSL and server.has_extn('STARTTLS') and port==SMTP_TLS_PORT:
                    context = ssl.create_default_context()
                    server.starttls(context=context)
                    server.ehlo()   # re-identify ourselves over TLS connection
                if kwargs.get('_output',None):
                    with open(kwargs.get('_output','.'), 'w') as fp:
                        fp.write(msg.as_string())
                else:
                    # 235, b'2.7.0 Authentication successful'
                    return_code, return_text = server.login(kwargs.get('_user',None),kwargs.get('_password',None))
                    if return_code >= 200 and return_code <= 299:
                        server.sendmail(kwargs.get('_sender',''), lstTo, msgRoot.as_string())
                        # append a mail message to an IMAP SEND folder
                        port = kwargs.get('_imap_port',IMAP4_PORT)  # or 143
                        if port==IMAP4_SSL_PORT:                    # SSL
                            imap = IMAP4_SSL(host=kwargs.get('_imapserver',''),port=port)
                        else:
                            imap = IMAP4(host=kwargs.get('_imapserver',''),port=port)
                       
                        if IS_SSL and kwargs.get('_imap_port',None) and kwargs.get('_imapserver',None):
                            # OK, [b'Logged in']
                            return_code, return_text = imap.login(kwargs.get('_user',None),kwargs.get('_password',None))
                            imap.append('Sent', '', Time2Internaldate(time.time()), str(msgRoot).encode('utf8'))
                            imap.logout()
                        return return_code, return_text
                    else:
                        return return_code, return_text
                return return_code, return_text
            else:
                return return_code, return_text
        else:
            return 401,'ERROR - eMail erroneo !!!'
    except smtplib.SMTPException as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPException <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPResponseException as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPResponseException <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPSenderRefused as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPSenderRefused <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPRecipientsRefused as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPRecipientsRefused <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPDataError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPDataError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPConnectError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPConnectError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPHeloError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPHeloError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPNotSupportedError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPNotSupportedError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPAuthenticationError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPAuthenticationError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except smtplib.SMTPNotSupportedError as e:
        tb = sys.exc_info()[2]
        LOG.error('SMTPNotSupportedError <%s> en linea %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    except Exception as e:
        tb = sys.exc_info()[2]
        LOG.error('sendEmail <%s> in line %s !!!' % (repr(e),tb.tb_lineno))
        server.quit()
        return 401,repr(e)
    finally:
        del msgRoot
        del server
        del imap
        gc.collect()


def getMail(*args,**kwargs):
    '''Recibir email POP3 o IMAP
       @Arguments:
           _user         - str  -  usuario
           _password     - str  -  password
           _imapserver   - str  -  servidor IMAP
           _pop3server   - str  -  servidor POP3
           _port         - str  -  puerto
           _criteria     - str  -   ALL - devuelve todos los mensajes que coinciden con el resto del criterio
                                    ANSWERED - coincide con los mensajes con la bandera \\ANSWERED establecida
                                    BCC "cadena" - coincide con los mensajes con "cadena" en el campo Bcc:
                                    BEFORE "fecha" - coincide con los mensajes con Date: antes de "fecha"
                                    BODY "cadena" - coincide con los mensajes con "cadena" en el cuerpo del mensaje
                                    CC "cadena" - coincide con los mensajes con "cadena" en el campo Cc:
                                    DELETED - coincide con los mensajes borrados
                                    FLAGGED - coincide con los mensajes con la bandera \\FLAGGED establecida (algunas veces referidos como Importante o Urgente)
                                    FROM "cadena" - coincide con los mensajes con "cadena" en el campo From:
                                    KEYWORD "cadena" - coincide con los mensajes con "cadena" como palabra clave
                                    NEW - coincide con los mensajes nuevos
                                    OLD - coincide con los mensajes antiguos
                                    ON "fecha" - coincide con los mensajes con Date: coincidiendo con "fecha"
                                    RECENT - coincide con los mensajes con la bandera \\RECENT establecida
                                  * SEEN - coincide con los mensajes que han sido leídos (la bandera \\SEEN esta estabecido)
                                    SINCE "fecha" - coincide con los mensajes con Date: despues de "fecha"
                                    SUBJECT "cadena" - coincide con los mensajes con "cadena" en Subject:
                                    TEXT "cadena" - coincide con los mensajes con el texto "cadena"
                                    TO "cadena" - coincide con los mensajes con "cadena" en To:
                                    UNANSWERED - coincide con los mensajes que no han sido respondidos
                                    UNDELETED - coincide con los mensajes que no están eliminados
                                    UNFLAGGED - coincide con los mensajes que no tienen bandera
                                    UNKEYWORD "cadena" - coincide con los mensajes que no tienen la palabra clave "cadena"
                                  * UNSEEN - coincide con los mensajes que aun no han sido leidos
                         ejemplo cuando se incluye más de un criterio :  imap.search(None, 'UNSEEN', 'FROM "%s"' % (sender_of_interest))
                                  
           _outputdir    - str  -  fichero donde grabara los correos leidos
       @Returns 200|401

    '''
    # for standard IMAP protocol port 143, no encryption
    # for standard IMAP protocol port 993, SSL
    # for standard IMAP protocol port 143, TLS


    try:
        input_data = []

        if kwargs.get('_imapserver',None):
            # Receive the email (this example assumes IMAP authentication is required)
            port = kwargs.get('_port',IMAP4_PORT)

            if port==IMAP4_SSL_PORT:
                server = IMAP4_SSL(kwargs['_imapserver'],port)
            else:
                server = IMAP4(kwargs['_imapserver'],port)

            if IS_SSL and port == IMAP4_PORT: # situar en NOAUTH
                context = ssl.create_default_context()
                server.starttls(ssl_context=context)

            server.login(kwargs.get('_user',None),kwargs.get('_password',None))   # AUTH

            #server.select(mailbox='INBOX', readonly=False)
            server.select(readonly=False)
            typ, data = server.search(None, kwargs.get('_criteria','UNSEEN'))
            if typ == 'OK':
                for msgid in data[0].split():
                    typ, data = server.fetch(msgid, '(RFC822)')
                    email_message=email.message_from_bytes(data[0][1])
                    # Header Details
                    date_tuple = email.utils.parsedate_tz(email_message['Date'])
                    if date_tuple:
                        local_date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
                        local_message_date = "%s" %((local_date.strftime("%a, %d %b %Y %H:%M:%S")))
                    email_from = (email.header.make_header(email.header.decode_header(email_message['From'])))
                    email_to = (email.header.make_header(email.header.decode_header(email_message['To'])))
                    email_subject = (email.header.make_header(email.header.decode_header(email_message['Subject'])))
                    # Body details
                    body_data = {'body':'','file':[]}
                    for part in email_message.walk():
                        
                        if part.get_content_type() == "text/plain":
                            body = bytes2str(part.get_payload(decode=True),'utf-8')
                            print("From: %s\nTo: %s\nDate: %s\nSubject: %s\n\nBody: \n\n%s" %(email_from, email_to,local_message_date, email_subject, body))
                            file_name = "email_" + bytes2str(msgid) + ".txt"
                            body_data['body'] = file_name
                            output_file = open(kwargs.get('_outputdir','') + file_name, 'w')

                            output_file.write("From: %s\nTo: %s\nDate: %s\nSubject: %s\n\nBody: \n\n%s" %(email_from, email_to,local_message_date, email_subject, body))
                            output_file.close()
                        if part.get_content_maintype() != 'multipart' and part.get('Content-Disposition') is not None:
                            body_data['file'].append(part.get_filename())
                            open(kwargs.get('_outputdir','') + part.get_filename(), 'wb').write(part.get_payload(decode=True))
                            

                        else:
                            continue
                    input_data.append(body_data)
                server.close()
            server.logout()
            del server
            return 200, 'application/json', json.dumps(input_data,default=to_json)
        else:
            return 401
    except Exception as e:
        tb = sys.exc_info()[2]
        LOG.error('getEmail <%s> in line %s !!!' % (repr(e),tb.tb_lineno))
        print('getEmail <%s> in line %s !!!' % (repr(e),tb.tb_lineno))
        return 401

def deleteIMAP(*args,**kwargs):
    '''Eliminar todos los correos de IMAP
       @Arguments:
           _user         - str  -  usuario
           _password     - str  -  password
           _imapserver   - str  -  servidor IMAP
           _port         - str  -  puerto
           _folder       - str  -  carpeta a tratar  'INPUT',
           _criteria     - str  -   ALL - devuelve todos los mensajes que coinciden con el resto del criterio
                                    ANSWERED - coincide con los mensajes con la bandera \\ANSWERED establecida
                                    BCC "cadena" - coincide con los mensajes con "cadena" en el campo Bcc:
                                    BEFORE "fecha" - coincide con los mensajes con Date: antes de "fecha"
                                    BODY "cadena" - coincide con los mensajes con "cadena" en el cuerpo del mensaje
                                    CC "cadena" - coincide con los mensajes con "cadena" en el campo Cc:
                                    DELETED - coincide con los mensajes borrados
                                    FLAGGED - coincide con los mensajes con la bandera \\FLAGGED establecida (algunas veces referidos como Importante o Urgente)
                                    FROM "cadena" - coincide con los mensajes con "cadena" en el campo From:
                                    KEYWORD "cadena" - coincide con los mensajes con "cadena" como palabra clave
                                    NEW - coincide con los mensajes nuevos
                                    OLD - coincide con los mensajes antiguos
                                    ON "fecha" - coincide con los mensajes con Date: coincidiendo con "fecha"
                                    RECENT - coincide con los mensajes con la bandera \\RECENT establecida
                                  * SEEN - coincide con los mensajes que han sido leídos (la bandera \\SEEN esta estabecido)
                                    SINCE "fecha" - coincide con los mensajes con Date: despues de "fecha"
                                    SUBJECT "cadena" - coincide con los mensajes con "cadena" en Subject:
                                    TEXT "cadena" - coincide con los mensajes con el texto "cadena"
                                    TO "cadena" - coincide con los mensajes con "cadena" en To:
                                    UNANSWERED - coincide con los mensajes que no han sido respondidos
                                    UNDELETED - coincide con los mensajes que no están eliminados
                                    UNFLAGGED - coincide con los mensajes que no tienen bandera
                                    UNKEYWORD "cadena" - coincide con los mensajes que no tienen la palabra clave "cadena"
                                  * UNSEEN - coincide con los mensajes que aun no han sido leidos
                         ejemplo cuando se incluye más de un criterio :  imap.search(None, 'UNSEEN', 'FROM "%s"' % (sender_of_interest))           
       @Returns 200|401
    '''

    try:
        if kwargs.get('_imapserver',None) and kwargs.get('_user',None) and kwargs.get('_password',None) and kwargs.get('_folder',None):
            port = kwargs.get('_port',IMAP4_PORT)
            
            if port==IMAP4_SSL_PORT:
                server = IMAP4_SSL(kwargs['_imapserver'],port)
            else:
                server = IMAP4(kwargs['_imapserver'],port)

            if IS_SSL and port == IMAP4_PORT: # situar en NOAUTH
                context = ssl.create_default_context()
                server.starttls(ssl_context=context)
                
            server.login(kwargs.get('_user',None),kwargs.get('_password',None))   # AUTH
            if kwargs.get('_folder',None):
                server.select(mailbox='INBOX', readonly=False)
            else:
                server.select(readonly=False)
                
            #typ, data = server.search(None, 'ALL')  # para eliminar todos los emails
            typ, data = server.search(None,kwargs.get('_criteria','DELETED'))
            if typ == 'OK':
                for msgid in data[0].split():
                    server.store(msgid, '+FLAGS', '\\Deleted')
                    # server.store(msgid, '+FLAGS', '\\Trash')
            server.expunge()
            server.close()
            server.logout()
            del server
            return 200
        else:
            return 401
    except Exception as e:
        tb = sys.exc_info()[2]
        LOG.error('deleteIMAP <%s> in line %s !!!' % (repr(e),tb.tb_lineno))
        return 401

def timeZone(timeZone):
    """Adaptar la hora segun la zona horaria"""
    zone = {'Madrid':2,'Londres':1,'Kabul':4.5}
    nowUTC = datetime.utcnow()
    return nowUTC + timedelta(hours=zone[timeZone])

if __name__ == "__main__":
    print ('''copyright {0}'''.format( __copyright__))
    print ('''license {0}'''.format( __license__))
    print ('''version {0}'''.format( __version__))
    if len(sys.argv) < 2:
        sys.stderr.write("for help use -h o --help")
    elif sys.argv[1]=='-h' or sys.argv[1]=='--help':
        print ('''
        Podra enviar fácilmente mensajes de correo electronico :\n\n''')

    #getMail(_user='pymetrick@pymetrick.org',_password='BETACA96',_imapserver='pymetrick.com',_port=993,_criteria='''SUBJECT "[OK] - Let's encrypt certificate issued"''',_outputdir='c:/tmp/prueba.txt')
    #f = getMail(_user='pymetrick@pymetrick.org',_password='BETACA96',_imapserver='pymetrick.com',_port=993,_criteria='''SUBJECT "envio"''',_outputdir='c:/tmp/')
    #print(f)
    #deleteIMAP(_user='pymetrick@pymetrick.org',_password='BETACA96',_imapserver='pymetrick.com',_port=993,_folder='INBOX',_criteria='''SUBJECT "envio"''')



