"""
AgentEmailTool - Email tool for AI agents
Uses AgentMail.to as primary SDK, with fallback to smtplib/imap-tools

This tool provides email capabilities for DAARION agents:
- Create/manage email inboxes
- Send/receive emails
- Email authentication (OTP, magic links)
- Analyze email content
"""

import os
import re
import json
import asyncio
import logging
import tempfile
import hashlib
import time
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime
from dataclasses import dataclass, field
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import aiofiles

# Security
from cryptography.fernet import Fernet
import base64

# Rate limiting
from collections import defaultdict
from threading import Lock

# AgentMail SDK
try:
    from agentmail import AgentMail, AsyncAgentMail
    AGENTMAIL_AVAILABLE = True
except ImportError:
    AGENTMAIL_AVAILABLE = False

# Fallback
try:
    import smtplib
    import imaplib
    from email.parser import Parser
    FALLBACK_AVAILABLE = True
except ImportError:
    FALLBACK_AVAILABLE = False

logger = logging.getLogger(__name__)


# ============================================================================
# CONFIGURATION & RATE LIMITING
# ============================================================================

@dataclass
class EmailConfig:
    """Email tool configuration"""
    agentmail_api_key: Optional[str] = None
    
    # Fallback SMTP/IMAP settings
    smtp_host: str = "smtp.gmail.com"
    smtp_port: int = 587
    imap_host: str = "imap.gmail.com"
    imap_port: int = 993
    
    # Security
    encryption_key: Optional[bytes] = None
    credentials_dir: str = "/tmp/agent_email_creds"
    temp_dir: str = "/tmp/agent_email_attachments"
    
    # Rate limiting
    rate_limit_per_minute: int = 8
    
    # PII protection
    redact_pii_in_logs: bool = True


class RateLimiter:
    """Simple rate limiter for email operations"""
    
    def __init__(self, max_per_minute: int = 8):
        self.max_per_minute = max_per_minute
        self.requests: Dict[str, List[float]] = defaultdict(list)
        self.lock = Lock()
    
    def is_allowed(self, agent_id: str) -> bool:
        """Check if request is allowed for agent"""
        with self.lock:
            now = time.time()
            # Clean old requests
            self.requests[agent_id] = [
                t for t in self.requests[agent_id]
                if now - t < 60
            ]
            # Check limit
            if len(self.requests[agent_id]) >= self.max_per_minute:
                return False
            # Add request
            self.requests[agent_id].append(now)
            return True
    
    def wait_time(self, agent_id: str) -> float:
        """Get seconds to wait before next request"""
        with self.lock:
            if agent_id not in self.requests or not self.requests[agent_id]:
                return 0
            oldest = min(self.requests[agent_id])
            return max(0, 60 - (time.time() - oldest))


# Global rate limiter
rate_limiter = RateLimiter()


# ============================================================================
# CREDENTIALS STORAGE (ENCRYPTED)
# ============================================================================

class SecureCredentialsStore:
    """Encrypted credentials storage using Fernet"""
    
    def __init__(self, config: EmailConfig):
        self.config = config
        self.credentials_dir = Path(config.credentials_dir)
        self.credentials_dir.mkdir(parents=True, exist_ok=True)
        
        # Initialize encryption
        if config.encryption_key:
            self.fernet = Fernet(config.encryption_key)
        else:
            # Generate key if not provided
            key = Fernet.generate_key()
            self.fernet = Fernet(key)
            # In production, store this key securely (e.g., in Second Me)
            logger.warning("No encryption key provided, generated temporary key")
    
    def _get_file_path(self, agent_id: str) -> Path:
        """Get credentials file path for agent"""
        # Hash agent_id for filename
        agent_hash = hashlib.sha256(agent_id.encode()).hexdigest()[:16]
        return self.credentials_dir / f"creds_{agent_hash}.enc"
    
    def save(self, agent_id: str, credentials: Dict[str, Any]) -> None:
        """Save encrypted credentials"""
        file_path = self._get_file_path(agent_id)
        data = json.dumps(credentials).encode()
        encrypted = self.fernet.encrypt(data)
        file_path.write_bytes(encrypted)
        logger.info(f"Credentials saved for agent: {self._redact_agent(agent_id)}")
    
    def load(self, agent_id: str) -> Optional[Dict[str, Any]]:
        """Load decrypted credentials"""
        file_path = self._get_file_path(agent_id)
        if not file_path.exists():
            return None
        try:
            encrypted = file_path.read_bytes()
            decrypted = self.fernet.decrypt(encrypted)
            return json.loads(decrypted)
        except Exception as e:
            logger.error(f"Failed to load credentials: {e}")
            return None
    
    def delete(self, agent_id: str) -> None:
        """Delete credentials"""
        file_path = self._get_file_path(agent_id)
        if file_path.exists():
            file_path.unlink()
            logger.info(f"Credentials deleted for agent: {self._redact_agent(agent_id)}")
    
    def _redact_agent(self, agent_id: str) -> str:
        """Redact agent_id for logging"""
        if len(agent_id) > 8:
            return f"{agent_id[:4]}...{agent_id[-4:]}"
        return "****"


# ============================================================================
# PII & SECURITY HELPERS
# ============================================================================

class PIIGuard:
    """PII protection for logging and auditing"""
    
    # Patterns to redact
    PATTERNS = [
        (r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[EMAIL]'),
        (r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b', '[CARD]'),
        (r'\b\d{6,10}\b', '[OTP]'),
        (r'(?i)(password|passwd|pwd)[=:\s]+\S+', '[PASSWORD]'),
        (r'(?i)(token|api[_-]?key|secret)[=:\s]+\S+', '[TOKEN]'),
    ]
    
    @classmethod
    def redact(cls, text: str) -> str:
        """Redact PII from text"""
        if not text:
            return text
        result = text
        for pattern, replacement in cls.PATTERNS:
            result = re.sub(pattern, replacement, result)
        return result
    
    @classmethod
    def redact_dict(cls, data: Dict) -> Dict:
        """Redact PII from dictionary"""
        return {k: cls.redact(str(v)) for k, v in data.items()}


class AuditLogger:
    """Audit log for email operations (no PII)"""
    
    def __init__(self, log_dir: str = "/tmp/agent_email_logs"):
        self.log_dir = Path(log_dir)
        self.log_dir.mkdir(parents=True, exist_ok=True)
    
    def log(self, agent_id: str, operation: str, details: Dict[str, Any]) -> None:
        """Log operation without PII"""
        safe_details = PIIGuard.redact_dict(details)
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "agent_id": hashlib.sha256(agent_id.encode()).hexdigest()[:16],
            "operation": operation,
            "details": safe_details
        }
        
        log_file = self.log_dir / f"audit_{datetime.utcnow().strftime('%Y%m%d')}.jsonl"
        with open(log_file, "a") as f:
            f.write(json.dumps(log_entry) + "\n")


# ============================================================================
# MAIN EMAIL TOOL CLASS
# ============================================================================

class AgentEmailTool:
    """
    Email tool for AI agents.
    
    Provides email inbox management, send/receive, and authentication automation.
    
    Usage:
        tool = AgentEmailTool(agent_id="sofiia")
        inbox = tool.create_inbox()
        tool.send(to=["user@example.com"], subject="Hello", body="Test")
        emails = tool.receive(unread_only=True)
    """
    
    def __init__(
        self,
        agent_id: str,
        config: Optional[EmailConfig] = None,
        api_key: Optional[str] = None
    ):
        """
        Initialize email tool for agent.
        
        Args:
            agent_id: Unique identifier for the agent
            config: Email configuration (uses env vars if not provided)
            api_key: AgentMail API key (overrides config)
        """
        self.agent_id = agent_id
        self.config = config or self._load_config()
        self.api_key = api_key or self.config.agentmail_api_key or os.getenv("AGENTMAIL_API_KEY")
        
        # Initialize storage and logging
        self.creds_store = SecureCredentialsStore(self.config)
        self.audit = AuditLogger()
        
        # Client (initialized lazily)
        self._client = None
        self._async_client = None
        
        # Temp directory for attachments
        self.temp_dir = Path(self.config.temp_dir)
        self.temp_dir.mkdir(parents=True, exist_ok=True)
    
    def _load_config(self) -> EmailConfig:
        """Load config from environment"""
        return EmailConfig(
            agentmail_api_key=os.getenv("AGENTMAIL_API_KEY"),
            encryption_key=os.getenv("EMAIL_ENCRYPTION_KEY", "").encode() or None,
            credentials_dir=os.getenv("EMAIL_CREDS_DIR", "/tmp/agent_email_creds"),
            rate_limit_per_minute=int(os.getenv("EMAIL_RATE_LIMIT", "8")),
        )
    
    @property
    def client(self):
        """Lazy-load AgentMail client"""
        if not self._client and AGENTMAIL_AVAILABLE and self.api_key:
            self._client = AgentMail(api_key=self.api_key)
        return self._client
    
    @property
    def async_client(self):
        """Lazy-load async AgentMail client"""
        if not self._async_client and AGENTMAIL_AVAILABLE and self.api_key:
            self._async_client = AsyncAgentMail(api_key=self.api_key)
        return self._async_client
    
    # ========================================================================
    # RATE LIMITING
    # ========================================================================
    
    def _check_rate_limit(self) -> None:
        """Check rate limit, raise if exceeded"""
        if not rate_limiter.is_allowed(self.agent_id):
            wait = rate_limiter.wait_time(self.agent_id)
            raise RateLimitError(f"Rate limit exceeded. Wait {wait:.1f}s")
    
    # ========================================================================
    # INBOX MANAGEMENT
    # ========================================================================
    
    def create_inbox(
        self,
        username: Optional[str] = None,
        domain: Optional[str] = None,
        display_name: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Create a new email inbox for the agent.
        
        Args:
            username: Optional username (defaults to agent_id) - IGNORED in AgentMail API
            domain: Optional domain - IGNORED in AgentMail API  
            display_name: Optional display name
            
        Returns:
            Dict with inbox details: inbox_id, email_address, api_key, etc.
            
        Raises:
            RateLimitError: If rate limit exceeded
            EmailError: If creation fails
        """
        self._check_rate_limit()
        
        try:
            if self.client:
                inbox = self.client.inboxes.create()
                
                result = {
                    "inbox_id": inbox.inbox_id,
                    "email_address": inbox.inbox_id,
                    "display_name": display_name or inbox.display_name,
                    "created_at": datetime.utcnow().isoformat(),
                    "provider": "agentmail"
                }
                
                # Store credentials
                self.creds_store.save(self.agent_id, {
                    "inbox_id": result["inbox_id"],
                    "email": result["email_address"],
                    "provider": "agentmail",
                    "created_at": result["created_at"]
                })
                
                self.audit.log(self.agent_id, "create_inbox", result)
                logger.info(f"Inbox created: {result['email_address']}")
                return result
            else:
                raise EmailError("AgentMail not available and fallback not implemented")
                
        except Exception as e:
            self.audit.log(self.agent_id, "create_inbox_error", {"error": str(e)})
            raise EmailError(f"Failed to create inbox: {e}")
    
    def list_inboxes(self) -> List[Dict[str, Any]]:
        """List all inboxes for this agent"""
        self._check_rate_limit()
        
        creds = self.creds_store.load(self.agent_id)
        if not creds:
            return []
        
        try:
            if self.client:
                response = self.client.inboxes.list()
                return [
                    {
                        "inbox_id": ib.inbox_id,
                        "email": ib.inbox_id,
                    }
                    for ib in response.inboxes
                ]
        except Exception as e:
            logger.error(f"Failed to list inboxes: {e}")
            return []
    
    def delete_inbox(self, inbox_id: Optional[str] = None) -> Dict[str, Any]:
        """
        Delete an inbox.
        
        Args:
            inbox_id: Specific inbox to delete (uses stored if not provided)
            
        Returns:
            Deletion confirmation
        """
        self._check_rate_limit()
        
        inbox_id = inbox_id or (self.creds_store.load(self.agent_id) or {}).get("inbox_id")
        if not inbox_id:
            raise EmailError("No inbox_id provided and no stored inbox found")
        
        try:
            if self.client:
                self.client.inboxes.delete(inbox_id=inbox_id)
                self.creds_store.delete(self.agent_id)
                self.audit.log(self.agent_id, "delete_inbox", {"inbox_id": inbox_id})
                return {"status": "deleted", "inbox_id": inbox_id}
        except Exception as e:
            raise EmailError(f"Failed to delete inbox: {e}")
    
    # ========================================================================
    # SENDING EMAILS
    # ========================================================================
    
    def send(
        self,
        to: List[str],
        subject: str,
        body: str,
        html: Optional[str] = None,
        attachments: Optional[List[str]] = None,
        cc: Optional[List[str]] = None,
        bcc: Optional[List[str]] = None,
        inbox_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Send an email.
        
        Args:
            to: Recipient email addresses
            subject: Email subject
            body: Plain text body
            html: HTML body (optional)
            attachments: List of file paths to attach
            cc: CC recipients
            bcc: BCC recipients
            inbox_id: Specific inbox to send from (uses stored if not provided)
            
        Returns:
            Dict with send confirmation: message_id, sent_at, etc.
            
        Raises:
            RateLimitError: If rate limit exceeded
            EmailError: If send fails
        """
        self._check_rate_limit()
        
        # Get inbox_id
        inbox_id = inbox_id or (self.creds_store.load(self.agent_id) or {}).get("inbox_id")
        if not inbox_id:
            raise EmailError("No inbox_id provided and no stored inbox found")
        
        # Process attachments securely
        attachment_paths = []
        if attachments:
            for path in attachments:
                p = Path(path)
                if p.exists() and p.is_file():
                    # Copy to temp dir (security)
                    temp_path = self.temp_dir / f"{time.time()}_{p.name}"
                    import shutil
                    shutil.copy2(p, temp_path)
                    attachment_paths.append(str(temp_path))
        
        try:
            if self.client:
                result = self.client.inboxes.messages.send(
                    inbox_id=inbox_id,
                    to=to,
                    subject=subject,
                    text=body,
                    html=html or f"<p>{body.replace(chr(10), '<br>')}</p>",
                    attachments=attachment_paths or None,
                    cc=cc,
                    bcc=bcc
                )
                
                response = {
                    "status": "sent",
                    "message_id": result.message_id if hasattr(result, 'message_id') else str(result),
                    "sent_at": datetime.utcnow().isoformat(),
                    "to": to,
                    "subject": PIIGuard.redact(subject)
                }
                
                self.audit.log(self.agent_id, "send_email", response)
                logger.info(f"Email sent: {subject}")
                
                # Cleanup temp attachments
                for path in attachment_paths:
                    try:
                        Path(path).unlink()
                    except:
                        pass
                
                return response
            else:
                # Fallback to smtplib
                return self._send_fallback(
                    to, subject, body, html, attachments, inbox_id
                )
                
        except Exception as e:
            self.audit.log(self.agent_id, "send_error", {"error": str(e)})
            raise EmailError(f"Failed to send email: {e}")
    
    def _send_fallback(
        self,
        to: List[str],
        subject: str,
        body: str,
        html: Optional[str],
        attachments: Optional[List[str]],
        inbox_id: str
    ) -> Dict[str, Any]:
        """Fallback send using smtplib"""
        if not FALLBACK_AVAILABLE:
            raise EmailError("No email provider available")
        
        creds = self.creds_store.load(self.agent_id)
        if not creds or not creds.get("smtp"):
            raise EmailError("No fallback credentials configured")
        
        smtp_creds = creds["smtp"]
        
        msg = MIMEMultipart('alternative')
        msg['Subject'] = subject
        msg['From'] = creds.get("email", inbox_id)
        msg['To'] = ", ".join(to)
        
        msg.attach(MIMEText(body, 'plain'))
        if html:
            msg.attach(MIMEText(html, 'html'))
        
        # Attachments
        for filepath in attachments or []:
            with open(filepath, 'rb') as f:
                part = MIMEBase('application', 'octet-stream')
                part.set_payload(f.read())
            encoders.encode_base64(part)
            part.add_header('Content-Disposition', f'attachment; filename={Path(filepath).name}')
            msg.attach(part)
        
        with smtplib.SMTP(smtp_creds["host"], smtp_creds["port"]) as server:
            server.starttls()
            server.login(smtp_creds["user"], smtp_creds["pass"])
            server.send_message(msg)
        
        return {"status": "sent", "provider": "fallback"}
    
    # ========================================================================
    # RECEIVING EMAILS
    # ========================================================================
    
    def receive(
        self,
        unread_only: bool = True,
        limit: int = 20,
        query: Optional[str] = None,
        inbox_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Receive emails.
        
        Args:
            unread_only: Only get unread messages
            limit: Maximum number of messages
            query: Search query
            inbox_id: Specific inbox (uses stored if not provided)
            
        Returns:
            List of email dicts with keys: id, from, to, subject, body, date, attachments, raw_html
        """
        self._check_rate_limit()
        
        inbox_id = inbox_id or (self.creds_store.load(self.agent_id) or {}).get("inbox_id")
        if not inbox_id:
            raise EmailError("No inbox_id provided")
        
        try:
            if self.client:
                labels = ["unread"] if unread_only else None
                response = self.client.inboxes.messages.list(
                    inbox_id=inbox_id,
                    limit=limit,
                    labels=labels
                )
                
                results = []
                for msg in response.messages:
                    results.append({
                        "id": msg.message_id,
                        "from": msg.from_,
                        "to": msg.to,
                        "subject": msg.subject,
                        "body": msg.preview,
                        "html": getattr(msg, "html_content", ""),
                        "date": str(msg.timestamp),
                        "attachments": msg.attachments or [],
                        "labels": msg.labels or [],
                        "raw_html": ""
                    })
                
                self.audit.log(self.agent_id, "receive_emails", {
                    "count": len(results),
                    "query": query
                })
                
                return results
            else:
                return self._receive_fallback(inbox_id, unread_only, limit)
                
        except Exception as e:
            self.audit.log(self.agent_id, "receive_error", {"error": str(e)})
            raise EmailError(f"Failed to receive emails: {e}")
    
    def _receive_fallback(
        self,
        inbox_id: str,
        unread_only: bool,
        limit: int
    ) -> List[Dict[str, Any]]:
        """Fallback receive using imaplib"""
        creds = self.creds_store.load(self.agent_id)
        if not creds or not creds.get("imap"):
            raise EmailError("No fallback credentials")
        
        imap_creds = creds["imap"]
        
        with imaplib.IMAP4_SSL(imap_creds["host"], imap_creds["port"]) as mail:
            mail.login(imap_creds["user"], imap_creds["pass"])
            mail.select('INBOX')
            
            status, data = mail.search(None, 'UNSEEN' if unread_only else 'ALL')
            email_ids = data[0].split()[-limit:]
            
            results = []
            for eid in email_ids:
                status, msg_data = mail.fetch(eid, '(RFC822)')
                email_message = Parser().parsestr(msg_data[0][1])
                
                results.append({
                    "id": email_message['Message-ID'],
                    "from": email_message['From'],
                    "to": email_message['To'],
                    "subject": email_message['Subject'],
                    "body": email_message.get_payload(),
                    "date": email_message['Date'],
                    "attachments": [],
                    "raw_html": ""
                })
            
            return results
    
    # ========================================================================
    # FORWARDING
    # ========================================================================
    
    def forward(
        self,
        email_id: str,
        to: List[str],
        inbox_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """Forward an email to new recipients."""
        self._check_rate_limit()
        
        # Get original email
        emails = self.receive(limit=100, inbox_id=inbox_id)
        original = next((e for e in emails if e["id"] == email_id), None)
        
        if not original:
            raise EmailError(f"Email {email_id} not found")
        
        # Forward with original content
        forwarded_body = f"""
---------- Forwarded message ----------
From: {original['from']}
Subject: {original['subject']}
Date: {original['date']}

{original['body']}
"""
        
        return self.send(
            to=to,
            subject=f"Fwd: {original['subject']}",
            body=forwarded_body,
            inbox_id=inbox_id
        )
    
    # ========================================================================
    # EMAIL ANALYSIS
    # ========================================================================
    
    def analyze_and_extract(self, email_dict: Dict[str, Any]) -> Dict[str, Any]:
        """
        Analyze email and extract structured information.
        
        Extracts:
        - summary: Brief summary of email
        - action_items: Any action items mentioned
        - credentials: OTP, tokens, passwords found
        - auth_urls: URLs for authentication
        - files: Any file references
        
        Args:
            email_dict: Email dict from receive()
            
        Returns:
            Dict with extracted information
        """
        body = email_dict.get("body", "")
        html = email_dict.get("html", "")
        subject = email_dict.get("subject", "")
        
        result = {
            "summary": self._extract_summary(subject, body),
            "action_items": self._extract_action_items(body),
            "credentials": self._extract_credentials(body),
            "auth_urls": self._extract_auth_urls(body + html),
            "files": self._extract_files(body + html),
            "sentiment": self._analyze_sentiment(body)
        }
        
        self.audit.log(self.agent_id, "analyze_email", {
            "email_id": email_dict.get("id", "unknown")
        })
        
        return result
    
    def _extract_summary(self, subject: str, body: str) -> str:
        """Extract brief summary"""
        lines = body.split('\n')
        preview = ' '.join(lines[:3])[:200]
        return f"Re: {subject} - {preview}..."
    
    def _extract_action_items(self, body: str) -> List[str]:
        """Extract action items"""
        action_patterns = [
            r'(?i)(?:please|kindly|could you|would you)\s+(.+?)(?:\.|$)',
            r'(?i)(?:action required|todo|to-do|deadline)\s*:?\s*(.+?)(?:\.|$)',
        ]
        
        items = []
        for pattern in action_patterns:
            items.extend(re.findall(pattern, body))
        return items[:5]
    
    def _extract_credentials(self, body: str) -> List[Dict[str, str]]:
        """Extract credentials/OTP/tokens from email"""
        credentials = []
        
        # OTP patterns
        otp_patterns = [
            r'(?i)(?:code|otp|verification code|one-time)\s*:?\s*(\d{4,8})',
            r'(?i)(\d{4,6})\s*(?:is your|is the|code)',
        ]
        
        for pattern in otp_patterns:
            matches = re.findall(pattern, body)
            for match in matches:
                credentials.append({
                    "type": "otp",
                    "value": match,
                    "redacted": f"{match[:2]}***{match[-2:]}"
                })
        
        return credentials
    
    def _extract_auth_urls(self, text: str) -> List[str]:
        """Extract authentication URLs"""
        url_pattern = r'https?://[^\s<>"\']+(?:reset|verify|login|auth|confirm|link|click)[^\s<>"\']*'
        urls = re.findall(url_pattern, text, re.IGNORECASE)
        return list(set(urls))[:5]
    
    def _extract_files(self, text: str) -> List[str]:
        """Extract file references"""
        patterns = [
            r'(?i)(?:attached|attachment|file):\s*([^\s]+)',
            r'(?i)\.pdf[^\s]*\.pdf',
            r'(?i)\.doc[^\s]*\.doc',
            r'(?i)\.xlsx?[^\s]*\.xls',
        ]
        
        files = []
        for pattern in patterns:
            files.extend(re.findall(pattern, text))
        return list(set(files))[:5]
    
    def _analyze_sentiment(self, body: str) -> str:
        """Simple sentiment analysis"""
        positive = ['thank', 'great', 'excellent', 'appreciate', 'welcome']
        negative = ['sorry', 'issue', 'problem', 'error', 'failed', 'urgent']
        
        body_lower = body.lower()
        
        pos_count = sum(1 for w in positive if w in body_lower)
        neg_count = sum(1 for w in negative if w in body_lower)
        
        if pos_count > neg_count:
            return "positive"
        elif neg_count > pos_count:
            return "negative"
        return "neutral"
    
    # ========================================================================
    # AUTHENTICATION AUTOMATION
    # ========================================================================
    
    async def use_for_auth(
        self,
        service_url: str,
        email_data: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Use email for authentication (OTP, magic links).
        
        Attempts to:
        1. Extract OTP/token from email
        2. Navigate to service and input OTP
        3. Or click magic link
        
        Args:
            service_url: URL of service to authenticate with
            email_data: Email dict containing OTP/link
            
        Returns:
            Dict with auth result: success, method, details
        """
        analysis = self.analyze_and_extract(email_data)
        
        result = {
            "service_url": service_url,
            "method": None,
            "success": False,
            "details": {}
        }
        
        # Try magic links first
        if analysis["auth_urls"]:
            # In production, would use browser automation
            result["method"] = "magic_link"
            result["details"]["urls_found"] = analysis["auth_urls"]
            result["success"] = True  # Would click link
        
        # Try OTP
        elif analysis["credentials"]:
            otp = analysis["credentials"][0]["value"]
            result["method"] = "otp"
            result["details"]["otp"] = analysis["credentials"][0]["redacted"]
            result["success"] = True  # Would input OTP
        
        self.audit.log(self.agent_id, "auth_attempt", result)
        return result
    
    # ========================================================================
    # ASYNC OPERATIONS
    # ========================================================================
    
    async def create_inbox_async(
        self,
        username: Optional[str] = None,
        domain: Optional[str] = None
    ) -> Dict[str, Any]:
        """Async version of create_inbox"""
        if not self.async_client:
            return self.create_inbox(username, domain)
        
        self._check_rate_limit()
        username = username or self.agent_id.lower().replace("_", "-")
        
        inbox = await self.async_client.inboxes.create(
            username=username,
            domain=domain
        )
        
        return {
            "inbox_id": str(inbox),
            "email_address": f"{username}@{domain or 'agentmail.to'}",
            "provider": "agentmail"
        }
    
    async def receive_async(
        self,
        unread_only: bool = True,
        limit: int = 20,
        inbox_id: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Async version of receive"""
        if not self.async_client:
            return self.receive(unread_only, limit, inbox_id=inbox_id)
        
        self._check_rate_limit()
        inbox_id = inbox_id or (self.creds_store.load(self.agent_id) or {}).get("inbox_id")
        
        messages = await self.async_client.inboxes.messages.list(
            inbox_id=inbox_id,
            limit=limit,
            unread_only=unread_only
        )
        
        return [
            {
                "id": str(m),
                "body": getattr(m, "text_content", "")
            }
            for m in messages
        ]


# ============================================================================
# ERROR CLASSES
# ============================================================================

class EmailError(Exception):
    """Base email error"""
    pass

class RateLimitError(EmailError):
    """Rate limit exceeded"""
    pass


# ============================================================================
# REGISTRATION FOR OCTOTOOLS
# ============================================================================

def register_tools() -> Dict[str, Any]:
    """
    Register AgentEmailTool with OctoTools.
    
    Returns:
        Dict of tool definitions
    """
    return {
        "agent_email": {
            "class": AgentEmailTool,
            "description": "Email tool for AI agents - send/receive emails, manage inboxes, automation",
            "methods": [
                "create_inbox",
                "list_inboxes", 
                "delete_inbox",
                "send",
                "receive",
                "forward",
                "analyze_and_extract",
                "use_for_auth"
            ]
        }
    }
