import imaplib
import smtplib
import ssl
import email
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from email.header import decode_header
from typing import List, Dict, Optional
from datetime import datetime
import re


PROVIDER_PRESET = {
    "gmail": {
        "imap_server": "imap.gmail.com",
        "smtp_server": "smtp.gmail.com",
        "imap_port": 993,
        "smtp_port": 587,
    },
    "outlook": {
        "imap_server": "outlook.office365.com",
        "smtp_server": "smtp.office365.com",
        "imap_port": 993,
        "smtp_port": 587,
    },
    "icloud": {
        "imap_server": "imap.mail.me.com",
        "smtp_server": "smtp.mail.me.com",
        "imap_port": 993,
        "smtp_port": 587,
    },
    "qq": {
        "imap_server": "imap.qq.com",
        "smtp_server": "smtp.qq.com",
        "imap_port": 993,
        "smtp_port": 587,
    }
}



class EmailService:
    """邮件服务类，支持 IMAP 和 SMTP 功能"""
    
    def __init__(self, 
                 provider: str,
                 email_address: str,
                 password: str):
        """
        初始化邮件服务
        
        Args:
            provider: 邮箱提供商
            email_address: 邮箱地址
            password: 邮箱密码或应用专用密码
        """
        if provider not in PROVIDER_PRESET:
            raise ValueError(f"Unsupported email serviceprovider: {provider}")
        self.provider = provider
        self.email_address = email_address
        self.password = password
        
        if not self.email_address or not self.password:
            raise ValueError("邮箱地址和密码必须提供")
    
    def _connect_imap(self) -> imaplib.IMAP4_SSL:
        """连接到 IMAP 服务器"""
        try:
            imap = imaplib.IMAP4_SSL(PROVIDER_PRESET[self.provider]['imap_server'], PROVIDER_PRESET[self.provider]['imap_port'])
            imap.login(self.email_address, self.password)
            return imap
        except Exception as e:
            raise ConnectionError(f"IMAP 连接失败: {str(e)}")
    
    def _connect_smtp(self) -> smtplib.SMTP:
        """连接到 SMTP 服务器"""
        try:
            smtp = smtplib.SMTP(PROVIDER_PRESET[self.provider]['smtp_server'], PROVIDER_PRESET[self.provider]['smtp_port'])
            smtp.starttls(context=ssl.create_default_context())
            smtp.login(self.email_address, self.password)
            return smtp
        except Exception as e:
            raise ConnectionError(f"SMTP 连接失败: {str(e)}")
    
    def _encode_folder_name(self, folder_name: str) -> str:
        """
        编码文件夹名称以适配 IMAP 命令
        
        Args:
            folder_name: 原始文件夹名称
            
        Returns:
            编码后的文件夹名称
        """
        if not folder_name:
            return 'INBOX'
        
        # 检查是否包含非 ASCII 字符（包括中文）
        has_non_ascii = any(ord(char) > 127 for char in folder_name)
        
        # 如果包含空格、特殊字符或非 ASCII 字符，需要用双引号包围
        if (' ' in folder_name or '[' in folder_name or ']' in folder_name or 
            has_non_ascii):
            return f'"{folder_name}"'
        
        return folder_name
    
    def _encode_imap_string(self, text: str, use_quotes: bool = True) -> str:
        """
        统一处理 IMAP 字符串编码
        
        Args:
            text: 要编码的字符串
            use_quotes: 是否使用引号包围
            
        Returns:
            编码后的字符串
        """
        if not text:
            return '""' if use_quotes else ''
        
        try:
            # 尝试使用 UTF-8 编码（IMAP 协议支持）
            text_bytes = text.encode('utf-8')
            if use_quotes:
                return f'"{text}"'
            else:
                return str(text_bytes)
        except UnicodeEncodeError:
            # 如果 UTF-8 编码失败，尝试使用 ASCII 兼容的部分
            try:
                text_ascii = text.encode('ascii', errors='ignore').decode('ascii')
                if text_ascii:
                    if use_quotes:
                        return f'"{text_ascii}"'
                    else:
                        return text_ascii
                else:
                    # 如果所有字符都被忽略，返回空字符串
                    return '""' if use_quotes else ''
            except Exception:
                # 最后的备用方案
                return '""' if use_quotes else ''
    
    def _decode_email_header(self, header: str) -> str:
        """解码邮件头信息"""
        try:
            decoded_parts = decode_header(header)
            decoded_string = ""
            for part, encoding in decoded_parts:
                if isinstance(part, bytes):
                    if encoding:
                        try:
                            decoded_string += part.decode(encoding)
                        except UnicodeDecodeError:
                            # 如果指定编码失败，尝试其他编码
                            for fallback_encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                                try:
                                    decoded_string += part.decode(fallback_encoding)
                                    break
                                except UnicodeDecodeError:
                                    continue
                            else:
                                # 如果所有编码都失败，使用 errors='ignore'
                                decoded_string += part.decode('utf-8', errors='ignore')
                    else:
                        # 尝试多种编码方式
                        for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                            try:
                                decoded_string += part.decode(encoding)
                                break
                            except UnicodeDecodeError:
                                continue
                        else:
                            decoded_string += part.decode('utf-8', errors='ignore')
                else:
                    decoded_string += str(part)
            return decoded_string
        except Exception as e:
            print(f"解码邮件头失败: {str(e)}")
            return str(header)
    
    def _parse_email_message(self, msg_data: bytes) -> Dict:
        """解析邮件消息"""
        email_message = email.message_from_bytes(msg_data)
        
        # 解析邮件头
        subject = self._decode_email_header(email_message.get('Subject', ''))
        from_addr = self._decode_email_header(email_message.get('From', ''))
        to_addr = self._decode_email_header(email_message.get('To', ''))
        date_str = email_message.get('Date', '')
        
        # 解析邮件内容
        body = ""
        content_type = ""
        
        if email_message.is_multipart():
            for part in email_message.walk():
                content_type_part = part.get_content_type()
                if content_type_part == "text/plain":
                    try:
                        payload = part.get_payload(decode=True)
                        if isinstance(payload, bytes):
                            # 尝试多种编码方式
                            for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                                try:
                                    body = payload.decode(encoding)
                                    break
                                except UnicodeDecodeError:
                                    continue
                            else:
                                # 如果所有编码都失败，使用 errors='ignore'
                                body = payload.decode('utf-8', errors='ignore')
                        else:
                            body = str(payload)
                        content_type = "text/plain"
                        break
                    except Exception as e:
                        print(f"解析纯文本内容失败: {str(e)}")
                        payload = part.get_payload(decode=True)
                        if isinstance(payload, bytes):
                            body = payload.decode('utf-8', errors='ignore')
                        else:
                            body = str(payload)
                        content_type = "text/plain"
                elif content_type_part == "text/html" and not body:
                    try:
                        payload = part.get_payload(decode=True)
                        if isinstance(payload, bytes):
                            # 尝试多种编码方式
                            for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                                try:
                                    body = payload.decode(encoding)
                                    break
                                except UnicodeDecodeError:
                                    continue
                            else:
                                # 如果所有编码都失败，使用 errors='ignore'
                                body = payload.decode('utf-8', errors='ignore')
                        else:
                            body = str(payload)
                        content_type = "text/html"
                    except Exception as e:
                        print(f"解析 HTML 内容失败: {str(e)}")
                        payload = part.get_payload(decode=True)
                        if isinstance(payload, bytes):
                            body = payload.decode('utf-8', errors='ignore')
                        else:
                            body = str(payload)
                        content_type = "text/html"
        else:
            try:
                payload = email_message.get_payload(decode=True)
                if isinstance(payload, bytes):
                    # 尝试多种编码方式
                    for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                        try:
                            body = payload.decode(encoding)
                            break
                        except UnicodeDecodeError:
                            continue
                    else:
                        # 如果所有编码都失败，使用 errors='ignore'
                        body = payload.decode('utf-8', errors='ignore')
                else:
                    body = str(payload)
                content_type = email_message.get_content_type()
            except Exception as e:
                print(f"解析邮件内容失败: {str(e)}")
                payload = email_message.get_payload(decode=True)
                if isinstance(payload, bytes):
                    body = payload.decode('utf-8', errors='ignore')
                else:
                    body = str(payload)
                content_type = email_message.get_content_type()
        
        return {
            'subject': subject,
            'from': from_addr,
            'to': to_addr,
            'date': date_str,
            'body': body,
            'content_type': content_type
        }
    
    def list_emails(self, folder: str = 'INBOX', limit: int = 50) -> List[Dict]:
        """
        列出邮件列表
        
        Args:
            folder: 邮件文件夹名称
            limit: 返回邮件数量限制
            
        Returns:
            邮件列表
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            _, message_numbers = imap.search(None, 'ALL')
            
            email_list = []
            message_list = message_numbers[0].split()
            
            # 获取最新的邮件
            for num in message_list[-limit:]:
                _, msg_data = imap.fetch(num, '(RFC822)')
                if msg_data and msg_data[0] and len(msg_data[0]) > 1:
                    msg_bytes = msg_data[0][1]
                    if isinstance(msg_bytes, bytes):
                        email_info = self._parse_email_message(msg_bytes)
                        email_info['uid'] = num.decode()
                        email_list.append(email_info)
            
            return email_list[::-1]  # 返回最新的邮件在前
        finally:
            imap.logout()
    
    def search_emails(self, 
                     query: Optional[str] = None,
                     from_addr: Optional[str] = None,
                     to_addr: Optional[str] = None,
                     subject: Optional[str] = None,
                     folder: str = 'INBOX',
                     limit: int = 50) -> List[Dict]:
        """
        搜索邮件
        
        Args:
            query: 搜索关键词
            from_addr: 发件人地址
            to_addr: 收件人地址
            subject: 主题关键词
            folder: 邮件文件夹名称
            limit: 返回邮件数量限制
            
        Returns:
            搜索结果列表
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            
            # 构建搜索条件，使用统一的编码处理
            search_criteria = []
            if query:
                encoded_query = self._encode_imap_string(query, use_quotes=False)
                if encoded_query:
                    search_criteria.append(f'TEXT {encoded_query}')
            if from_addr:
                encoded_from = self._encode_imap_string(from_addr, use_quotes=False)
                if encoded_from:
                    search_criteria.append(f'FROM {encoded_from}')
            if to_addr:
                encoded_to = self._encode_imap_string(to_addr, use_quotes=False)
                if encoded_to:
                    search_criteria.append(f'TO {encoded_to}')
            if subject:
                encoded_subject = self._encode_imap_string(subject, use_quotes=False)
                if encoded_subject:
                    search_criteria.append(f'SUBJECT {encoded_subject}')
            
            if not search_criteria:
                search_criteria.append('ALL')
            
            search_string = ' '.join(search_criteria)
            _, message_numbers = imap.search(None, search_string)
            
            email_list = []
            message_list = message_numbers[0].split()
            
            # 获取搜索结果
            for num in message_list[-limit:]:
                _, msg_data = imap.fetch(num, '(RFC822)')
                if msg_data and msg_data[0] and len(msg_data[0]) > 1:
                    msg_bytes = msg_data[0][1]
                    if isinstance(msg_bytes, bytes):
                        email_info = self._parse_email_message(msg_bytes)
                        email_info['uid'] = num.decode()
                        email_list.append(email_info)
            
            return email_list[::-1]
        finally:
            imap.logout()
    
    def get_email_by_uid(self, uid: str, folder: str = 'INBOX') -> Optional[Dict]:
        """
        根据 UID 获取特定邮件
        
        Args:
            uid: 邮件 UID
            folder: 邮件文件夹名称
            
        Returns:
            邮件信息
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            _, msg_data = imap.fetch(uid, '(RFC822)')
            
            if not msg_data or not msg_data[0] or len(msg_data[0]) <= 1:
                return None
            
            msg_bytes = msg_data[0][1]
            if not isinstance(msg_bytes, bytes):
                return None
            
            email_info = self._parse_email_message(msg_bytes)
            email_info['uid'] = uid
            return email_info
        finally:
            imap.logout()
    
    def delete_email(self, uid: str, folder: str = 'INBOX') -> str:
        """
        删除邮件
        
        Args:
            uid: 邮件 UID
            folder: 邮件文件夹名称
            
        Returns:
            删除是否成功
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            imap.store(uid, '+FLAGS', '\\Deleted')
            imap.expunge()
            return "Email deleted successfully"
        except Exception as e:
            print(f"删除邮件失败: {str(e)}")
            return "Email deletion failed"
        finally:
            imap.logout()
    
    def move_email(self, uid: str, from_folder: str = 'INBOX', to_folder: str = 'Trash') -> str:
        """
        移动邮件到其他文件夹
        
        Args:
            uid: 邮件 UID
            from_folder: 源文件夹
            to_folder: 目标文件夹
            
        Returns:
            移动是否成功
        """
        imap = self._connect_imap()
        try:
            encoded_from_folder = self._encode_folder_name(from_folder)
            encoded_to_folder = self._encode_folder_name(to_folder)
            
            imap.select(encoded_from_folder)
            _, msg_data = imap.fetch(uid, '(RFC822)')
            
            if not msg_data or not msg_data[0] or len(msg_data[0]) <= 1:
                return "Email not found"
            
            msg_bytes = msg_data[0][1]
            if not isinstance(msg_bytes, bytes):
                return "Email not found"
            
            # 复制到目标文件夹
            imap.append(encoded_to_folder, '', imaplib.Time2Internaldate(datetime.now()), msg_bytes)
            
            # 从源文件夹删除
            imap.store(uid, '+FLAGS', '\\Deleted')
            imap.expunge()
            
            return "Email moved successfully"
        except Exception as e:
            print(f"移动邮件失败: {str(e)}")
            return "Email movement failed"
        finally:
            imap.logout()
    
    def send_email(self, 
                  to_address: str,
                  subject: str,
                  body: str,
                  cc: Optional[List[str]] = None,
                  bcc: Optional[List[str]] = None,
                  attachments: Optional[List[str]] = None,
                  html_body: Optional[str] = None) -> bool:
        """
        发送邮件
        
        Args:
            to_address: 收件人地址
            subject: 邮件主题
            body: 邮件正文
            cc: 抄送地址列表
            bcc: 密送地址列表
            attachments: 附件文件路径列表
            html_body: HTML 格式的邮件正文
            
        Returns:
            发送是否成功
        """
        smtp = self._connect_smtp()
        try:
            # 创建邮件消息
            msg = MIMEMultipart('alternative')
            msg['From'] = self.email_address
            msg['To'] = to_address
            msg['Subject'] = subject
            
            if cc:
                msg['Cc'] = ', '.join(cc)
            
            # 添加文本内容
            text_part = MIMEText(body, 'plain', 'utf-8')
            msg.attach(text_part)
            
            # 添加 HTML 内容
            if html_body:
                html_part = MIMEText(html_body, 'html', 'utf-8')
                msg.attach(html_part)
            
            # 添加附件
            if attachments:
                for file_path in attachments:
                    if os.path.exists(file_path):
                        with open(file_path, 'rb') as attachment:
                            part = MIMEBase('application', 'octet-stream')
                            part.set_payload(attachment.read())
                        
                        encoders.encode_base64(part)
                        # 处理附件文件名编码
                        filename = os.path.basename(file_path)
                        try:
                            # 尝试使用 UTF-8 编码文件名
                            filename_encoded = filename.encode('utf-8').decode('utf-8')
                        except UnicodeEncodeError:
                            # 如果失败，使用 ASCII 兼容的部分
                            filename_encoded = filename.encode('ascii', errors='ignore').decode('ascii')
                            if not filename_encoded:
                                filename_encoded = 'attachment'
                        
                        part.add_header(
                            'Content-Disposition',
                            f'attachment; filename= {filename_encoded}'
                        )
                        msg.attach(part)
            
            # 发送邮件
            all_recipients = [to_address]
            if cc:
                all_recipients.extend(cc)
            if bcc:
                all_recipients.extend(bcc)
            
            smtp.sendmail(self.email_address, all_recipients, msg.as_string())
            return True
            
        except Exception as e:
            print(f"发送邮件失败: {str(e)}")
            return False
        finally:
            smtp.quit()
    
    def get_folders(self) -> List[str]:
        """
        获取所有邮件文件夹
        
        Returns:
            文件夹名称列表
        """
        imap = self._connect_imap()
        try:
            _, folders = imap.list()
            folder_list = []
            for folder in folders:
                if isinstance(folder, bytes):
                    try:
                        folder_str = folder.decode('utf-8')
                    except UnicodeDecodeError:
                        # 如果 UTF-8 解码失败，尝试其他编码
                        try:
                            folder_str = folder.decode('latin-1')
                        except UnicodeDecodeError:
                            folder_str = folder.decode('ascii', errors='ignore')
                else:
                    folder_str = str(folder)
                
                # 解析文件夹名称，提取完整的文件夹路径
                # IMAP LIST 返回格式: (\HasNoChildren) "/" "INBOX"
                # 或者: (\HasNoChildren) "/" "[Gmail]/Sent Mail"
                match = re.search(r'"/" "([^"]+)"', folder_str)
                if match:
                    folder_name = match.group(1)
                    # 处理文件夹名称的编码 - 尝试多种编码方式
                    folder_name_decoded = None
                    for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                        try:
                            # 如果文件夹名称是字节字符串，先解码
                            if isinstance(folder_name, bytes):
                                folder_name_decoded = folder_name.decode(encoding)
                            else:
                                # 如果是字符串，尝试编码再解码
                                folder_name_decoded = folder_name.encode(encoding).decode(encoding)
                            break
                        except (UnicodeError, UnicodeDecodeError, UnicodeEncodeError):
                            continue
                    
                    if folder_name_decoded:
                        folder_list.append(folder_name_decoded)
                    else:
                        folder_list.append(folder_name)
                else:
                    # 备用解析方法
                    parts = folder_str.split('"')
                    if len(parts) >= 3:
                        folder_name = parts[-2]
                        # 处理文件夹名称的编码 - 尝试多种编码方式
                        folder_name_decoded = None
                        for encoding in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
                            try:
                                # 如果文件夹名称是字节字符串，先解码
                                if isinstance(folder_name, bytes):
                                    folder_name_decoded = folder_name.decode(encoding)
                                else:
                                    # 如果是字符串，尝试编码再解码
                                    folder_name_decoded = folder_name.encode(encoding).decode(encoding)
                                break
                            except (UnicodeError, UnicodeDecodeError, UnicodeEncodeError):
                                continue
                        
                        if folder_name_decoded:
                            folder_list.append(folder_name_decoded)
                        else:
                            folder_list.append(folder_name)
            
            return folder_list
        finally:
            imap.logout()
    
    def get_email_count(self, folder: str = 'INBOX') -> int:
        """
        获取文件夹中的邮件数量
        
        Args:
            folder: 邮件文件夹名称
            
        Returns:
            邮件数量
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            _, message_numbers = imap.search(None, 'ALL')
            return len(message_numbers[0].split())
        finally:
            imap.logout()
    
    def mark_as_read(self, uid: str, folder: str = 'INBOX') -> bool:
        """
        标记邮件为已读
        
        Args:
            uid: 邮件 UID
            folder: 邮件文件夹名称
            
        Returns:
            操作是否成功
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            imap.store(uid, '+FLAGS', '\\Seen')
            return True
        except Exception as e:
            print(f"标记已读失败: {str(e)}")
            return False
        finally:
            imap.logout()
    
    def mark_as_unread(self, uid: str, folder: str = 'INBOX') -> bool:
        """
        标记邮件为未读
        
        Args:
            uid: 邮件 UID
            folder: 邮件文件夹名称
            
        Returns:
            操作是否成功
        """
        imap = self._connect_imap()
        try:
            encoded_folder = self._encode_folder_name(folder)
            imap.select(encoded_folder)
            imap.store(uid, '-FLAGS', '\\Seen')
            return True
        except Exception as e:
            print(f"标记未读失败: {str(e)}")
            return False
        finally:
            imap.logout()
