from __future__ import annotations
from inspect import signature
import logging
from io import BytesIO
from random import randint
import time
from typing import Any, Callable, Generator, Union
import requests

author = "krypton-byte"
logging.basicConfig(format='%(asctime)s  %(message)s', level=logging.INFO)
log = logging.getLogger('xtempmail')
log.setLevel(logging.WARNING)
# log=logging.getLogger('xtempmail')
# log.setLevel(logging.INFO)
class Mail_ID_NOTFOUND(Exception):
    pass

class Parameters(Exception):
    pass
class Extension:
    def __init__(self, ex):
        self.ex = '@'+ex
    def __repr__(self):
        return self.ex
    def __str__(self):
        return self.ex

extension = [ Extension(i) for i in ['mailto.plus','fexpost.com','fexbox.org','fexbox.ru','mailbox.in.ua','rover.info','inpwa.com','intopwa.com','tofeat.com','chitthi.in']]

class event:
    def __init__(self) -> None:
        self.messages = []
    def message(self, filter:Callable[[EmailMessage], Any]=None):
        def messag(f: Union[Callable[[EmailMessage], None], None]):
            if callable(filter):
                sig = signature(filter)
                if sig.parameters.keys().__len__() > 1 or sig.parameters.keys().__len__() < 1:
                    raise Parameters('1 Parameters Required For filter Parameter')
                log.info(f'Filter Parameter: {list(sig.parameters.keys())[0]}')
            if not callable(f):
                raise TypeError('Is not function')
            sig = signature(f)
            if sig.parameters.keys().__len__() > 1 or sig.parameters.keys().__len__() < 1:
                raise Parameters('1 Parameters Required For Callback function')
            log.info(f'Callback Parameter: {list(sig.parameters.keys())[0]}')
            self.messages.append((f,filter if callable(filter) else (lambda x:x)))
        return messag
    def on_message(self, data):
        for i in self.messages:
            if i[1](data):
                i[0](data)

def warn_mail(e):
    if '@'+e.split('@')[-1] not in list(map(lambda x:x.__str__(), extension)):
        log.warning(f'[!] @{e.split("@")[-1]} unsupported\n')

class StrangerMail:
    def __init__(self, account, stranger: str) -> None:
        self.email = stranger
        self.account:Email = account
    def send_message(self, subject: str, text: str, file=None, filename='file', multiply_file=[]):
        return self.account.send_mail(self.email, subject, text, file, filename, multiply_file)
    def __repr__(self):
        return self.email

class Attachment:
    def __init__(self, mail, mail_id: int, attachment_id:int, content_id: str, name: str, size:int ) -> None:
        self.mail = mail
        self.mail_id = mail_id
        self.id = attachment_id
        self.content_id = content_id
        self.name = name
        self.size = size
    def download(self, filename=False)->Union[BytesIO, None]:
        """
        :param filename: str->save as file, bool -> BytesIO
        """
        log.info(f'Download File, Attachment ID: {self.id} FROM: {self.mail.__repr__()} NAME: {self.name.__repr__()}')
        bins=requests.get(f'https://tempmail.plus/api/mails/{self.mail_id}/attachments/{self.id}',params={'email':self.mail, 'epin':''})
        if isinstance(filename, str):
            open(filename,'wb').write(bins.content)
        else:
            return BytesIO(bins.content)
    def __repr__(self):
        return f'<["{self.name}" size:{self.size}]>'

class EmailMessage:
    def __init__(self, **kwargs) -> None:
        self.attachments: list[Attachment]=[]
        self.from_mail = StrangerMail(kwargs['to'], kwargs['from_mail'])
        for i in kwargs.pop('attachments',{}):
            attach = Attachment(**i, mail_id=kwargs['to'].email, mail=kwargs['from_mail'])
            self.attachments.append(attach)
        self.date: str = kwargs["date"]
        self.from_is_local:bool = kwargs["from_is_local"]
        self.from_name: str = kwargs["from_name"]
        self.html: str = kwargs["html"]
        self.is_tls: bool = kwargs["is_tls"]
        self.mail_id: int = kwargs["mail_id"]
        self.message_id: str = kwargs["message_id"]
        self.result: bool = kwargs["result"]
        self.subject: str = kwargs["subject"]
        self.text: str = kwargs["text"]
        self.to: Email = kwargs["to"]
    def delete(self)->bool:
        return self.to.delete_message(self.mail_id)
    def __repr__(self) -> str:
        return f'<[from:{self.to} subject:"{self.subject}"" attachment: {self.attachments.__len__()}]>'
        
class Email(requests.Session):
    def __init__(self, name:str, ext:Extension = extension[0], interval = 1) -> None:
        super().__init__()
        self.interval = interval
        self.email = name+ext.__str__()
        self.first_id = randint(10000000, 99999999)
        self.email_id = []
        self.on = event()
        log.info(f'Email: {self.email} Interval: {self.interval}')
    def get_all_message(self)->list:
        data = []
        log.info('Get All Message')
        for mail in self.get(f'https://tempmail.plus/api/mails',params={'email':self.email, 'first_id':self.first_id, 'epin':''}).json()['mail_list']:
            data.append(self.get_mail(mail['mail_id']))
        return data

    def get_new_message(self)->Generator:
        while True:
            try:
                for mail in self.get(f'https://tempmail.plus/api/mails',params={'email':self.email, 'first_id':self.first_id, 'epin':''}).json()['mail_list']:
                    if mail['mail_id'] not in self.email_id:
                        recv=self.get_mail(mail['mail_id'])
                        log.info(f'New message from {mail["from_mail"]} subject: {mail["subject"].__repr__()} ID: {mail["mail_id"]}')
                        self.email_id.append(mail['mail_id'])
                        yield recv
            except requests.exceptions.SSLError:
                True
            time.sleep(self.interval)

    def get_mail(self, id: str)->EmailMessage:
        """
        Get Message Content
        :param id:mail_id
        """
        to=self.get(f'https://tempmail.plus/api/mails/{id}', params={'email':self.email, 'first_id':self.first_id, 'epin':''}).json()
        to['to'] = self
        log.info(f'Get Message From ID: {id.__repr__()}')
        return EmailMessage(**to)

    def delete_message(self, id: int)->bool:
        """
        :param id: mail_id
        """
        id in self.email_id and self.email_id.remove(id)
        status = self.delete(f'https://tempmail.plus/api/mails/{id}', data={'email':self.email, 'epin':''}).json()['result']
        if status:
            log.info('Email Message Successfully deleted')
        else:
            log.warn('Email Message not found')
            raise Mail_ID_NOTFOUND()
        return status

    def destroy(self)->bool:
        """
        Destroy Inbox
        """
        stat=self.delete('https://tempmail.plus/api/mails/', data={'email':self.email, 'first_id':self.first_id, 'epin':''}).json().get('result')
        log.info('Inbox Destroyed')
        return stat
    def send_mail(self, to: str, subject: str, text: str, file = None, filename = 'file',multiply_file = [])->bool:
        """
        :param to: Email [str | StrangerMail] -> Not Support external email (gmail, yahoo, etc)
        :param subject: str
        :param text: str
        :param file: filename/file path
        :param filename: str
        :param multiply_file: tuple (BytesIO|path, str)
        """
        warn_mail(to)
        log.info(f'Send Message From: {self.email.__repr__()} To: {to.__repr__()} Subject: {subject.__repr__()} Attachment: {bool(file or multiply_file)}')
        files = []
        to = to.email if isinstance(to, StrangerMail) else to
        if file:
            if isinstance(file, str):
                if filename and isinstance(filename, str):
                    files.append(('file',(filename,open(file,'rb').read())))
                else:
                    files.append(('file',open(file,'rb')))
            elif isinstance(file, BytesIO):
                files.append(('file',(filename,file.getvalue())))
        for i in multiply_file:
            if i.__len__() == 1:
                files.append(('file',open(i[0],'rb')))
            elif i.__len__() > 1:
                x=('file', (i[0],i[1].getvalue()))
                files.append(x)
        return self.post('https://tempmail.plus/api/mails/', data={'email': self.email,'to': to,'subject': subject,'content_type': 'text/html','text': text},files=tuple(files)).json()['result']

    def listen_new_message(self):
        log.info('Listen Message')
        for i in self.get_new_message():
            self.on.on_message(i)

    def __repr__(self) -> str:
        return f'<({self.email})>'

    def __str__(self):
        return self.email