import threading
import time
import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Set
import logging
from fetch import EmailChecker


class EmailTracker:
    """Track processed emails to avoid duplicate processing"""
    
    def __init__(self, tracking_file: str = "processed_emails.json"):
        """
        Initialize email tracker
        
        Args:
            tracking_file: Path to JSON file for storing processed email IDs
        """
        self.tracking_file = tracking_file
        self.processed_emails: Dict[str, Dict] = {}  # email_id -> {timestamp, email_data}
        self.lock = threading.Lock()
        self.logger = logging.getLogger(__name__)
        self._load_tracking_data()
    
    def _load_tracking_data(self):
        """Load processed email IDs from file"""
        if os.path.exists(self.tracking_file):
            try:
                with open(self.tracking_file, 'r') as f:
                    data = json.load(f)
                    self.processed_emails = data.get('processed_emails', {})
                self.logger.info(f"Loaded {len(self.processed_emails)} processed email IDs from {self.tracking_file}")
            except Exception as e:
                self.logger.error(f"Error loading tracking data: {e}")
                self.processed_emails = {}
        else:
            self.logger.info(f"Tracking file {self.tracking_file} does not exist. Starting fresh.")
    
    def _save_tracking_data(self):
        """Save processed email IDs to file"""
        try:
            with open(self.tracking_file, 'w') as f:
                json.dump({
                    'processed_emails': self.processed_emails,
                    'last_updated': datetime.now().isoformat()
                }, f, indent=2)
        except Exception as e:
            self.logger.error(f"Error saving tracking data: {e}")
    
    def is_email_processed(self, email_id: str) -> bool:
        """Check if an email has already been processed"""
        with self.lock:
            return email_id in self.processed_emails
    
    def mark_email_processed(self, email_id: str, email_data: Optional[Dict] = None):
        """
        Mark an email as processed
        
        Args:
            email_id: Unique email ID from Microsoft Graph
            email_data: Optional email data to store
        """
        with self.lock:
            self.processed_emails[email_id] = {
                'timestamp': datetime.now().isoformat(),
                'email_data': email_data or {}
            }
            self._save_tracking_data()
            self.logger.debug(f"Marked email {email_id} as processed")
    
    def cleanup_old_records(self, days: int = 30):
        """
        Remove tracking records older than specified days
        
        Args:
            days: Number of days to keep records (default: 30)
        """
        cutoff_date = datetime.now() - timedelta(days=days)
        with self.lock:
            initial_count = len(self.processed_emails)
            self.processed_emails = {
                email_id: data for email_id, data in self.processed_emails.items()
                if datetime.fromisoformat(data['timestamp']) > cutoff_date
            }
            removed = initial_count - len(self.processed_emails)
            if removed > 0:
                self._save_tracking_data()
                self.logger.info(f"Cleaned up {removed} old tracking records (older than {days} days)")
    
    def get_processed_count(self) -> int:
        """Get total number of processed emails"""
        with self.lock:
            return len(self.processed_emails)


class EmailPoller:
    """Background service that polls for new emails"""
    
    def __init__(self, email_checker: EmailChecker, sender_email: str = "scheduling@usetwine.com",
                 folder: str = "inbox", interval: int = 60, tracking_file: str = "processed_emails.json",
                 callback_url: Optional[str] = None):
        """
        Initialize email poller
        
        Args:
            email_checker: EmailChecker instance for fetching emails
            sender_email: Email address to filter by
            folder: Email folder to check (default: inbox)
            interval: Polling interval in seconds (default: 60)
            tracking_file: Path to email tracking file
            callback_url: Optional URL to call when new email is processed
        """
        self.email_checker = email_checker
        self.sender_email = sender_email
        self.folder = folder
        self.interval = interval
        self.callback_url = callback_url
        
        self.tracker = EmailTracker(tracking_file)
        self.logger = logging.getLogger(__name__)
        
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._lock = threading.Lock()
        
        self.last_check_time: Optional[datetime] = None
        self.last_check_timestamp: Optional[str] = None
        self.emails_processed_count = 0
        self.last_error: Optional[str] = None
        
        # Load last check timestamp from state file
        self.state_file = "poller_state.json"
        self._load_state()
    
    def _load_state(self):
        """Load polling state from file"""
        if os.path.exists(self.state_file):
            try:
                with open(self.state_file, 'r') as f:
                    state = json.load(f)
                    self.last_check_timestamp = state.get('last_check_timestamp')
                    if self.last_check_timestamp:
                        try:
                            self.last_check_time = datetime.fromisoformat(self.last_check_timestamp)
                        except:
                            pass
                    self.emails_processed_count = state.get('emails_processed_count', 0)
                self.logger.info(f"Loaded polling state: last_check={self.last_check_timestamp}, processed={self.emails_processed_count}")
            except Exception as e:
                self.logger.error(f"Error loading state: {e}")
    
    def _save_state(self):
        """Save polling state to file"""
        try:
            with open(self.state_file, 'w') as f:
                json.dump({
                    'last_check_timestamp': self.last_check_timestamp,
                    'emails_processed_count': self.emails_processed_count,
                    'last_updated': datetime.now().isoformat()
                }, f, indent=2)
        except Exception as e:
            self.logger.error(f"Error saving state: {e}")
    
    def start_polling(self):
        """Start the polling service in a background thread"""
        with self._lock:
            if self._running:
                self.logger.warning("Polling is already running")
                return False
            
            self._running = True
            self._stop_event.clear()
            self._thread = threading.Thread(target=self._polling_loop, daemon=True)
            self._thread.start()
            self.logger.info(f"Email polling started (interval: {self.interval} seconds)")
            return True
    
    def stop_polling(self, timeout: int = 5):
        """
        Stop the polling service gracefully
        
        Args:
            timeout: Maximum time to wait for thread to stop (seconds)
        """
        with self._lock:
            if not self._running:
                self.logger.warning("Polling is not running")
                return False
            
            self.logger.info("Stopping email polling...")
            self._running = False
            self._stop_event.set()
        
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=timeout)
            if self._thread.is_alive():
                self.logger.warning("Polling thread did not stop within timeout")
            else:
                self.logger.info("Email polling stopped")
        
        return True
    
    def is_running(self) -> bool:
        """Check if polling is currently running"""
        with self._lock:
            return self._running
    
    def _polling_loop(self):
        """Main polling loop that runs in background thread"""
        self.logger.info("Polling loop started")
        
        while self._running:
            try:
                # Check for new emails
                self.check_for_new_emails()
                
                # Wait for next interval (or until stop event is set)
                if self._stop_event.wait(timeout=self.interval):
                    # Stop event was set, exit loop
                    break
                    
            except Exception as e:
                self.logger.error(f"Error in polling loop: {e}", exc_info=True)
                self.last_error = str(e)
                # Wait a bit before retrying on error
                if self._stop_event.wait(timeout=min(30, self.interval)):
                    break
        
        self.logger.info("Polling loop ended")
    
    def check_for_new_emails(self):
        """Check for new emails and process them"""
        try:
            self.logger.debug("Checking for new emails...")
            
            # Determine from_date: use last check time if available, otherwise use default
            from_date = None
            if self.last_check_timestamp:
                # Use last check time minus 1 minute buffer to ensure we don't miss emails
                try:
                    last_check = datetime.fromisoformat(self.last_check_timestamp)
                    from_date = (last_check - timedelta(minutes=1)).strftime("%Y-%m-%d")
                except:
                    pass
            
            # Fetch emails from sender
            emails = self.email_checker.check_emails_from_sender(
                sender_email=self.sender_email,
                folder=self.folder,
                unread_only=False,
                top=100,
                from_date=from_date
            )
            
            self.logger.info(f"Found {len(emails)} email(s) from {self.sender_email}")
            
            # Process new emails
            new_emails_count = 0
            for email in emails:
                email_id = email.get('id')
                if not email_id:
                    continue
                
                # Check if already processed
                if not self.tracker.is_email_processed(email_id):
                    # Process new email
                    self.process_new_email(email)
                    new_emails_count += 1
                else:
                    self.logger.debug(f"Email {email_id} already processed, skipping")
            
            if new_emails_count > 0:
                self.logger.info(f"Processed {new_emails_count} new email(s)")
            
            # Update state
            self.last_check_time = datetime.now()
            self.last_check_timestamp = self.last_check_time.isoformat()
            self.emails_processed_count += new_emails_count
            self.last_error = None
            self._save_state()
            
        except Exception as e:
            self.logger.error(f"Error checking for new emails: {e}", exc_info=True)
            self.last_error = str(e)
            raise
    
    def process_new_email(self, email: Dict):
        """
        Process a new email
        
        Args:
            email: Email dictionary from EmailChecker
        """
        email_id = email.get('id')
        subject = email.get('subject', 'No Subject')
        
        self.logger.info(f"Processing new email: {email_id} - {subject}")
        
        try:
            # Mark as processed
            self.tracker.mark_email_processed(email_id, email)
            
            # Extract details are already in email['extracted_details']
            extracted_details = email.get('extracted_details', {})
            
            self.logger.info(f"Email processed successfully:")
            self.logger.info(f"  Subject: {subject}")
            self.logger.info(f"  From: {email.get('from', 'Unknown')}")
            self.logger.info(f"  Date: {email.get('date', 'Unknown')}")
            if extracted_details:
                self.logger.info(f"  Name: {extracted_details.get('name', 'N/A')}")
                self.logger.info(f"  Phone: {extracted_details.get('phone', 'N/A')}")
                self.logger.info(f"  Email: {extracted_details.get('email', 'N/A')}")
            
            # Call callback URL if configured
            if self.callback_url:
                self._call_callback(email)
            
        except Exception as e:
            self.logger.error(f"Error processing email {email_id}: {e}", exc_info=True)
            raise
    
    def _call_callback(self, email: Dict):
        """Call external callback URL with email data"""
        if not self.callback_url:
            return
        
        try:
            import requests
            response = requests.post(
                self.callback_url,
                json={
                    'email': email,
                    'timestamp': datetime.now().isoformat(),
                    'source': 'email_poller'
                },
                timeout=10
            )
            response.raise_for_status()
            self.logger.info(f"Callback to {self.callback_url} successful")
        except Exception as e:
            self.logger.warning(f"Callback to {self.callback_url} failed: {e}")
    
    def set_interval(self, interval: int):
        """Update polling interval"""
        if interval < 10:
            raise ValueError("Interval must be at least 10 seconds")
        self.interval = interval
        self.logger.info(f"Polling interval updated to {interval} seconds")
    
    def get_status(self) -> Dict:
        """Get current polling status"""
        with self._lock:
            return {
                'is_running': self._running,
                'interval': self.interval,
                'last_check_time': self.last_check_timestamp,
                'emails_processed_count': self.emails_processed_count,
                'sender_email': self.sender_email,
                'folder': self.folder,
                'last_error': self.last_error,
                'processed_emails_tracked': self.tracker.get_processed_count()
            }

