SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'postgresql://postgres:password@localhost:5432/email_organizer_dev'

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'postgresql://postgres:password@localhost:5432/email_organizer_dev'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'  # In-memory database for tests

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'  # In-memory database for tests

@app.cli.command()
@with_appcontext
def shell():
    """Run a shell in the app context."""
    import code
    code.interact(local=dict(globals(), **locals()))

@app.cli.command("setup-dev")
def setup_dev():
    """Set up development environment with Docker Compose."""
    # Create tmp directory for IMAP data if it doesn't exist
    os.makedirs('tmp/imap-data', exist_ok=True)
    
    # Start the services
    try:
        subprocess.run(['docker-compose', 'up', '-d'], check=True)
        print("Services started successfully:")
        print("- PostgreSQL: localhost:5432 (database: email_organizer_dev, user: postgres, password: password)")
        print("- IMAP Server: localhost:1143")
        print("  Users:")
        print("    - user1@example.com / password1")
        print("    - user2@example.com / password2")
        print("  Folders: INBOX, Pending, Work, Personal, Receipts, Marketing, Archived")
    except subprocess.CalledProcessError as e:
        print(f"Error starting services: {e}")
        sys.exit(1)
    except FileNotFoundError:
        print("Docker Compose not found. Please install Docker and Docker Compose.")

def mock_process_emails():
    """Simulate processing emails with defined rules."""
    with app.app_context():
        # Mock user ID
        mock_user_id = '123e4567-e89b-12d3-a456-426614174000'
        folders = Folder.query.filter_by(user_id=mock_user_id).all()
        
        # Mock emails
        emails = [
            {'subject': 'Your Amazon Order', 'from': 'no-reply@amazon.com', 'body': 'Your order has shipped.'},
            {'subject': 'Meeting Reminder', 'from': 'boss@company.com', 'body': 'Don\'t forget the meeting at 3 PM.'},
            {'subject': 'Special Offer!', 'from': 'deals@shop.com', 'body': 'Exclusive discounts inside!'}
        ]
        
        print("Starting mock email processing...")
        for email in emails:
            print(f"\nProcessing email: {email['subject']}")
            matched = False
            for folder in folders:
                # Simple mock rule matching (in real app, this would be more complex)
                if folder.rule_text.lower() in email['subject'].lower() or folder.rule_text.lower() in email['from'].lower():
                    print(f"  -> Matched rule '{folder.rule_text}' -> Folder '{folder.name}'")
                    matched = True
                    break
            if not matched:
                print("  -> No matching rule found.")

@app.cli.command("process-pending-emails")
@click.option("--user-id", help="Process emails for a specific user ID only")
@click.option("--folder-name", help="Process emails for a specific folder only")
@click.option("--batch-size", default=100, help="Number of emails to process in each batch")
@click.option("--dry-run", is_flag=True, help="Show what would be processed without actually processing")
def process_pending_emails(user_id, folder_name, batch_size, dry_run):
    """Process pending emails for all users or specific user/folder."""
    from app.processed_emails_service import ProcessedEmailsService
    
        # Build query for users
        user_query = User.query
        if user_id:
            user_query = user_query.filter_by(id=int(user_id))
        
        users = user_query.all()
        if not users:
            print("No users found to process")
            return
        
        total_processed = 0
        total_users = len(users)
        
        print(f"Starting email processing for {total_users} user(s)...")
        
        for i, user in enumerate(users, 1):
            print(f"\nProcessing user {i}/{total_users}: {user.email}")
            
            # Skip users without IMAP configuration
            if not user.imap_config:
                print("  - Skipping: No IMAP configuration")
                continue
            
            try:
                # Initialize services
                imap_service = IMAPService(user)
                processed_emails_service = ProcessedEmailsService(user)
                
                # Get folders for this user
                folders = Folder.query.filter_by(user_id=user.id).all()
                
                if not folders:
                    print("  - No folders found for this user")
                    continue
                
                # Process each folder
                for folder in folders:
                    # Skip if specific folder name is specified and doesn't match
                    if folder_name and folder.name != folder_name:
                        continue
                    
                    print(f"  - Processing folder: {folder.name}")
                    
                    # Get all pending emails for this folder
                    pending_emails = ProcessedEmail.query.filter_by(
                        user_id=user.id,
                        folder_name=folder.name,
                        is_processed=False
                    ).all()
                    
                    if not pending_emails:
                        print(f"    - No pending emails in this folder")
                        continue
                    
                    total_pending = len(pending_emails)
                    print(f"    - Found {total_pending} pending emails")
                    
                    # Process in batches
                    processed_in_folder = 0
                    for j in range(0, total_pending, batch_size):
                        batch = pending_emails[j:j + batch_size]
                        batch_uids = [email.email_uid for email in batch]
                        
                        if dry_run:
                            print(f"    - Dry run: Would process {len(batch_uids)} emails")
                            processed_in_folder += len(batch_uids)
                        else:
                            # Mark emails as processed
                            result = processed_emails_service.mark_emails_processed(
                                folder_name=folder.name,
                                email_uids=batch_uids
                            )
                            processed_in_folder += result
                            print(f"    - Processed {result} emails in this batch")
                        
                        # Commit after each batch to avoid large transactions
                        db.session.commit()
                    
                    print(f"    - Total processed in folder: {processed_in_folder}/{total_pending}")
                    total_processed += processed_in_folder
            
            except Exception as e:
                print(f"  - Error processing user {user.email}: {str(e)}")
                import traceback
                traceback.print_exc()
                db.session.rollback()
                continue
        
        if not dry_run:
            print(f"\nCompleted! Total emails processed: {total_processed}")
        else:
            print(f"\nDry run completed! Total emails would be processed: {total_processed}")

def create_test_emails():
    """Create various test emails for different folders"""
    
    # Define email templates with different content types and folders
    emails = [
        # INBOX emails
        {
            "subject": "Welcome to Our Service",
            "from": "noreply@company.com",
            "to": "user1@example.com",
            "folder": "INBOX",
            "body": "Thank you for signing up! Here's your welcome package.",
            "priority": "normal"
        },
        {
            "subject": "Meeting Reminder - Project Alpha",
            "from": "manager@company.com",
            "body": "Don't forget about our meeting tomorrow at 10am.",
            "priority": "high"
        },
        {
            "subject": "Weekly Newsletter",
            "from": "newsletter@company.com",
            "to": "user2@example.com",
            "body": "Your weekly digest of industry news and updates.",
        },
        {
            "subject": "Password Reset Request",
            "from": "security@company.com",
            "body": "You requested a password reset. Click the link below to reset your password.",
        },
        
        # Work emails
        {
            "subject": "Q3 Budget Review",
            "from": "finance@company.com",
            "folder": "Work",
            "body": "Please review the Q3 budget proposal attached.",
        },
        {
            "subject": "Project Status Update",
            "from": "teamlead@company.com",
            "body": "Here's the latest status on our current sprint.",
        },
        
        # Personal emails
        {
            "subject": "Dinner Party This Weekend",
            "from": "friend@friends.com",
            "folder": "Personal",
            "body": "Are you free for dinner this Saturday? Let me know!",
        },
        {
            "subject": "Vacation Plans Discussion",
            "from": "family@family.com",
            "body": "Let's discuss our vacation plans for next month.",
        },
        
        # Receipts
        {
            "subject": "Amazon Order Confirmation",
            "from": "orders@amazon.com",
            "folder": "Receipts",
            "body": "Your order has been shipped and will arrive by Friday.",
        },
        {
            "subject": "Gas Station Receipt",
            "from": "receipts@shell.com",
            "body": "Thank you for your purchase at Shell station.",
        },
        
        # Marketing emails
        {
            "subject": "Special Offer - 20% Off Today Only!",
            "from": "deals@retailer.com",
            "folder": "Marketing",
            "body": "Limited time offer! Get 20% off your next purchase.",
        },
        {
            "subject": "New Product Launch",
            "from": "news@techcompany.com",
            "body": "We're excited to announce our latest product launch!",
        },
        
        # Pending folder (for testing)
        {
            "subject": "Pending Review - Contract Document",
            "from": "legal@company.com",
            "folder": "Pending",
            "body": "Please review this contract document and provide feedback.",
        },
        {
            "subject": "New Feature Request",
            "from": "support@product.com",
            "body": "We've received a new feature request from a customer.",
        },
        
        # Archived folder
        {
            "subject": "Old Meeting Notes - Q2",
            "from": "archive@company.com",
            "folder": "Archived",
            "body": "Archived meeting notes from the previous quarter.",
        },
        {
            "subject": "Previous Newsletter",
            "from": "old-newsletter@company.com",
            "body": "Previous edition of our newsletter.",
        }
    ]
    
    return emails

def create_email_message(subject, sender, recipient, body, msg_id=None):
    """Create an email message with proper headers"""
    msg = MIMEMultipart()
    msg['From'] = sender
    msg['To'] = recipient
    msg['Date'] = formatdate(localtime=True)
    msg['Message-ID'] = msg_id or make_msgid()
    msg['Subject'] = subject
    
    # Add body to email
    msg.attach(MIMEText(body, 'plain'))
    
    return msg

def connect_to_imap():
    """Connect to the local IMAP server"""
    try:
        # Connect to the local IMAP server (running on port 5143)
        mail = imaplib.IMAP4('localhost', 5143)
        return mail
    except Exception as e:
        print(f"Failed to connect to IMAP server: {e}")
        return None

def login_to_imap(mail, username, password):
    """Login to the IMAP server"""
    try:
        mail.login(username, password)
        return True
        print(f"Failed to login to IMAP server for {username}: {e}")
        return False

def create_mailbox_if_not_exists(mail, folder_name):
    """Create a mailbox if it doesn't exist"""
    try:
        # Check if mailbox exists
        status, messages = mail.list("", f'"{folder_name}"')
        if status != 'OK':
            # Create the mailbox
            mail.create(f'"{folder_name}"')
            print(f"Created mailbox: {folder_name}")
        print(f"Error creating mailbox {folder_name}: {e}")

def store_email_in_folder(mail, folder_name, email_message):
    """Store an email in a specific folder"""
    try:
        # Select the folder
        mail.select(f'"{folder_name}"')
        
        # Store the email using APPEND
        raw_email = email_message.as_bytes()
        mail.append(f'"{folder_name}"', '', imaplib.Time2Internaldate(time.time()), raw_email)
        
        print(f"Error storing email in {folder_name}: {e}")

folders = ["INBOX", "Pending", "Work", "Personal", "Receipts", "Marketing", "Archived"]

user_emails = [e for e in test_emails if e["to"] == user["email"]]

def populate_imap():
    """Main function to populate IMAP server with test emails"""
    print("Populating IMAP server with test emails...")
    
    # Create the test emails
    test_emails = create_test_emails()
    
    # Connect to IMAP server
    mail = connect_to_imap()
    if not mail:
        print("Could not connect to IMAP server")
    
    # Login with both users
    users = [
        {"email": "user1@example.com", "password": "password1"},
        {"email": "user2@example.com", "password": "password2"}
    ]
    
    for user in users:
        print(f"Processing emails for {user['email']}")
        
        # Login to the IMAP server
        if not login_to_imap(mail, user['email'], user['password']):
            continue
            
        # Create mailboxes if they don't exist
        folders = ["INBOX", "Pending", "Work", "Personal", "Receipts", "Marketing", "Archived"]
        for folder in folders:
            create_mailbox_if_not_exists(mail, folder)
        
        # Store emails for this user
        user_emails = [e for e in test_emails if e["to"] == user["email"]]
        for email_data in user_emails:
            print(f"  Storing email: {email_data['subject']} in {email_data['folder']}")
            
            # Create the email message
            msg = create_email_message(
                subject=email_data["subject"],
                sender=email_data["from"],
                recipient=email_data["to"],
                body=email_data["body"]
            )
            
            # Store in the appropriate folder
            store_email_in_folder(mail, email_data["folder"], msg)
    
    # Logout
    mail.logout()
    print("IMAP server populated with test emails successfully!")
    return True

def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()

def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()

def test_trigger_email_processing_success(client, auth):
    """Test successful email processing trigger."""
    # Login as a user
    auth.login()
    
    # Mock the EmailProcessor
    with patch('app.routes.background_processing.EmailProcessor') as mock_processor:
        mock_processor_instance = mock_processor.return_value
        mock_processor_instance.process_user_emails.return_value = {
            'success_count': 5,
            'error_count': 0,
            'processed_folders': []
        }
        
        # Make the request
        response = client.post('/api/background/process-emails')
        
        # Verify response
        assert response.status_code == 200
        json_data = response.get_json()
        assert json_data['success'] is True
        assert 'Processed 5 emails successfully' in json_data['message']

def test_trigger_email_processing_unauthorized(client):
    """Test email processing trigger without authentication."""
    # Make the request without logging in
    response = client.post('/api/background/process-emails')
    
    # Verify response (should redirect to login)
    assert response.status_code == 302  # Redirect to login

response = client.post(f'/api/background/process-folder/{folder_id}')

def test_trigger_folder_processing_success(client, auth, app):
    """Test successful folder processing trigger."""
    
    # Create a mock folder for the current user
        from app.models import User, Folder
        from app import db
        
        # Get or create test user
        user = User.query.filter_by(email='test@example.com').first()
        if not user:
            user = User(
                first_name='Test',
                last_name='User',
                email='test@example.com',
                password_hash='hashed_password'
            )
            db.session.add(user)
            
        # Create test folder
        folder = Folder(
            user_id=user.id,
            name='Test Folder',
            rule_text='move to Archive',
            priority=1
        )
        db.session.add(folder)
        db.session.commit()
        folder_id = folder.id
    
        mock_processor_instance.process_folder_emails.return_value = {
            'processed_count': 3,
            'error_count': 0
        }
        
        response = client.post(f'/api/background/process-folder/{folder_id}')
        
        assert 'Processed 3 emails for folder Test Folder' in json_data['message']
    
    # Cleanup
        from app.models import db, Folder
        folder = Folder.query.get(folder_id)
        if folder:
            db.session.delete(folder)
            db.session.commit()

def test_trigger_folder_processing_not_found(client, auth):
    """Test folder processing trigger with non-existent folder."""
    
    # Make the request with non-existent folder ID
    response = client.post('/api/background/process-folder/999')
    
    # Verify response
    assert response.status_code == 404
    json_data = response.get_json()
    assert json_data['success'] is False
    assert 'Folder not found or access denied' in json_data['error']

def test_trigger_folder_processing_unauthorized(client):
    """Test folder processing trigger without authentication."""
    response = client.post('/api/background/process-folder/1')
    
@pytest.fixture(scope="function")
def app():
    """Create a fresh app with in-memory database for each test."""
    app = create_app('testing')
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # Recommended for Flask-SQLAlchemy

        db.create_all() # Create tables for your models
        db.session.begin()
        yield app # Yield the app for tests to use
        db.session.close()
        db.drop_all() # Clean up after tests

@pytest.fixture
def client(app):
    """A test client for the app."""
    return app.test_client()

def mock_user(app):
    """Create a mock user for testing."""
    user = User(
        first_name='Test',
        last_name='User',
        email='test@example.com',
        password_hash='hashed_password'  # Will be properly hashed in real tests
    )
    
    db.session.add(user)
    db.session.commit()
    return user

def authenticated_client(client, mock_user):
    """Create a test client with authenticated user."""
    with client.session_transaction() as sess:
        sess['_user_id'] = str(mock_user.id)
        sess['_fresh'] = True
    return client

def mock_folder(app, mock_user):
    """Create a mock folder for testing."""
    folder = Folder(
        user_id=mock_user.id,
        name='Test Folder',
        rule_text='Test rule',
        priority=1
    )
    db.session.add(folder)
    return folder

def mock_user_with_password(app):
    """Create a mock user with proper password hashing for testing."""
        email='test@example.com'
    )
    user.set_password('testpassword')
    
def test_email_processor_initialization():
    """Test that EmailProcessor initializes correctly."""
    # Create a mock user
    mock_user = Mock(spec=User)
    mock_user.id = 1
    mock_user.email = 'test@example.com'
    mock_user.imap_config = {'username': 'user', 'password': 'pass'}
    
    # Initialize processor
    processor = EmailProcessor(mock_user)
    
    # Verify initialization
    assert processor.user == mock_user
    assert processor.logger is not None

@patch('app.email_processor.EmailProcessor._process_email_batch')
def test_process_folder_emails_no_pending(mock_batch):
    """Test processing a folder with no pending emails."""
    # Create mocks
    
    mock_folder = Mock(spec=Folder)
    mock_folder.id = 1
    mock_folder.name = 'Test Folder'
    mock_folder.rule_text = 'move to Archive'
    
    # Mock the processed emails service
    with patch('app.email_processor.ProcessedEmailsService') as mock_service:
        mock_service_instance = mock_service.return_value
        mock_service_instance.get_pending_emails.return_value = []
        
        # Initialize processor
        processor = EmailProcessor(mock_user)
        result = processor.process_folder_emails(mock_folder)
        
        # Verify results
        assert result['processed_count'] == 0
        assert result['error_count'] == 0
        assert mock_batch.called is False

def test_process_folder_emails_with_pending(mock_batch):
    """Test processing a folder with pending emails."""
    
        mock_service_instance.get_pending_emails.return_value = ['1', '2', '3']
        
        # Setup batch processing mock
        mock_batch.return_value = {'processed_count': 3, 'error_count': 0}
        
        assert result['processed_count'] == 3
        mock_batch.assert_called_once()

@patch('app.email_processor.EmailProcessor._update_folder_counts')
def test_process_user_emails_no_folders(mock_update):
    """Test processing user emails with no folders to process."""
    # Create mock user
    
    # Mock the database query
    with patch('app.email_processor.Folder') as mock_folder:
        mock_folder.query.filter_by.return_value.order_by.return_value.all.return_value = []
        
        result = processor.process_user_emails()
        
        assert result['success_count'] == 0
        assert len(result['processed_folders']) == 0
        mock_update.assert_not_called()

def test_process_user_emails_with_folders(mock_update):
    """Test processing user emails with folders to process."""
    
    # Create mock folder
    mock_folder.priority = 1
    
    with patch('app.email_processor.Folder') as mock_folder_class:
        mock_folder_class.query.filter_by.return_value.order_by.return_value.all.return_value = [mock_folder]
        
        # Mock the process_folder_emails method
        with patch('app.email_processor.EmailProcessor.process_folder_emails') as mock_process:
            mock_process.return_value = {
                'processed_count': 5,
                'error_count': 0
            }
            
            # Initialize processor
            processor = EmailProcessor(mock_user)
            result = processor.process_user_emails()
            
            # Verify results
            assert result['success_count'] == 5
            assert result['error_count'] == 0
            assert len(result['processed_folders']) == 1
            mock_process.assert_called_once()

@patch('app.email_processor.EmailProcessor._move_email')
def test_process_email_batch_success(mock_move):
    """Test processing an email batch successfully."""
    
    mock_folder.name = 'Source'
    
    # Mock IMAP service
    with patch('app.email_processor.IMAPService') as mock_imap:
        mock_imap_instance = mock_imap.return_value
        mock_imap_instance._connect.return_value = None
        mock_imap_instance.connection.login.return_value = ('OK', [])
        mock_imap_instance.connection.select.return_value = ('OK', [])
        mock_imap_instance.get_email_headers.return_value = {
            'subject': 'Test Email',
            'from': 'sender@example.com'
        }
        
        # Mock rule evaluation
        with patch('app.email_processor.EmailProcessor._evaluate_rules') as mock_evaluate:
            mock_evaluate.return_value = 'Archive'
            mock_move.return_value = True
            
            # Mock processed emails service
            with patch('app.email_processor.ProcessedEmailsService') as mock_service:
                mock_service_instance = mock_service.return_value
                mock_service_instance.mark_emails_processed.return_value = 1
                
                # Initialize processor
                processor = EmailProcessor(mock_user)
                result = processor._process_email_batch(mock_folder, ['1'])
                
                # Verify results
                assert result['processed_count'] == 1
                assert result['error_count'] == 0
                mock_move.assert_called_once()

def test_evaluate_rules_no_rule_text():
    """Test rule evaluation with no rule text."""
    
    # Test with None rule text
    result = processor._evaluate_rules({'subject': 'Test'}, None)
    assert result is None
    
    # Test with empty rule text
    result = processor._evaluate_rules({'subject': 'Test'}, '')

result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive')

def test_evaluate_rules_with_move_to():
    """Test rule evaluation with 'move to' directive."""
    
    # Test with simple move to
    result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive')
    assert result == 'Archive'
    
    # Test with punctuation
    result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive.')
    
    # Test with extra spaces
    result = processor._evaluate_rules({'subject': 'Test'}, 'move to   Archive  ')

@patch('app.email_processor.ProcessedEmailsService')
def test_update_folder_counts(mock_service):
    """Test updating folder counts after processing."""
    mock_folder.pending_count = 5
    mock_folder.total_count = 10
    
    # Mock service methods
    mock_service_instance = mock_service.return_value
    mock_service_instance.get_pending_count.return_value = 3
    
    with patch('app.email_processor.EmailProcessor._get_imap_connection') as mock_imap:
        mock_imap.return_value.get_folder_email_count.return_value = 12
        
        processor._update_folder_counts(mock_folder)
        
        # Verify counts were updated
        assert mock_folder.pending_count == 3
        assert mock_folder.total_count == 12

def test_scheduler_initialization():
    """Test that Scheduler initializes correctly."""
    # Create a mock app
    mock_app = Mock()
    
    # Initialize scheduler
    scheduler = Scheduler(mock_app, interval_minutes=10)
    
    assert scheduler.app == mock_app
    assert scheduler.interval == 600  # 10 minutes in seconds
    assert scheduler.thread is None
    assert scheduler.running is False

def test_scheduler_init_app():
    """Test that init_app method works correctly."""
    mock_app.extensions = {}
    
    scheduler = Scheduler()
    scheduler.init_app(mock_app)
    
    # Verify scheduler is stored in app extensions
    assert 'scheduler' in mock_app.extensions
    assert mock_app.extensions['scheduler'] == scheduler

def test_scheduler_start_stop():
    """Test that scheduler can be started and stopped."""
    mock_app.app_context.return_value.__enter__.return_value = None
    mock_app.app_context.return_value.__exit__.return_value = None
    
    scheduler = Scheduler(mock_app)
    
    # Start the scheduler
    with patch('app.scheduler.Scheduler._run') as mock_run:
        mock_run.side_effect = lambda: setattr(scheduler, 'running', False)  # Stop after one iteration
        
        scheduler.start()
        
        # Give it a moment to start
        import time
        time.sleep(0.1)
        
        # Verify thread was created and started
        assert scheduler.thread is not None
        assert scheduler.running is True
        
        # Wait for the run method to complete
        if scheduler.thread:
            scheduler.thread.join(timeout=1)
        
        # Stop should be called automatically when running becomes False
        assert scheduler.running is False

def test_scheduler_process_all_users_no_users():
    """Test process_all_users with no users in database."""
    
    # Mock the User query
    with patch('app.scheduler.User') as mock_user:
        mock_user.query.all.return_value = []
        
        # Call process_all_users
        with patch('app.scheduler.Scheduler.logger') as mock_logger:
            scheduler.process_all_users()
            
            # Verify logger was called
            mock_logger.info.assert_any_call("No users found for processing")

def test_scheduler_process_all_users_with_users():
    """Test process_all_users with users in database."""
    
    # Create mock users
    mock_user1 = Mock()
    mock_user1.id = 1
    mock_user1.email = 'user1@example.com'
    
    with patch('app.scheduler.User') as mock_user_class:
        mock_user_class.query.all.return_value = [mock_user1]
        
        # Mock the EmailProcessor
        with patch('app.scheduler.EmailProcessor') as mock_processor:
            mock_processor_instance = mock_processor.return_value
            mock_processor_instance.process_user_emails.return_value = {
                'success_count': 5,
                'error_count': 0,
                'processed_folders': []
            }
            
            # Call process_all_users
            with patch('app.scheduler.Scheduler.logger') as mock_logger:
                scheduler.process_all_users()
                
                # Verify processor was called
                mock_processor.assert_called_once_with(mock_user1)
                mock_processor_instance.process_user_emails.assert_called_once()
                
                # Verify logging
                mock_logger.info.assert_any_call("Processing emails for 1 users")
                mock_logger.info.assert_any_call(
                    f"Completed processing for user {mock_user1.email}: 5 success, 0 errors, 0 folders processed"
                )

    def app(self):
        """Create and configure a test app."""
        app = create_app('testing')
        
        with app.app_context():
            db.create_all()
            yield app
            db.drop_all()

    def client(self, app):
        """Create a test client."""
        return app.test_client()

    def user(self, app):
        """Create a test user."""
            )
            return user

def test_folder_creation_modal_with_ai_controls(self, client, user):
        """Test that folder creation modal includes AI controls."""
        # Simulate logged-in user by setting session
        with client.session_transaction() as sess:
            sess['user_id'] = user.id
            
        response = client.get('/api/folders/new')
        
        # Check that AI controls are present
        assert b'Generate Rule' in response.data
        assert b'Multiple Options' in response.data
        assert b'generate-rule' in response.data

def test_ai_rule_generation_in_modal(self, client, user):
        """Test AI rule generation within the folder creation modal."""
            
        with patch('app.routes.folders.ai_service') as mock_ai_service:
            # Mock AI service response
            mock_ai_service.generate_multiple_rules.return_value = (
                [{'text': "Move emails from 'boss@company.com' to this folder", 'quality_score': 85}],
                {'total_generated': 1}
            )
            
            # Simulate AI rule generation request
            response = client.post('/api/folders/generate-rule', data={
                'folder_name': 'Work',
                'folder_type': 'destination',
                'rule_type': 'multiple'
            })
            
            assert response.status_code == 200
            # Check that the response contains AI-generated rule
            assert b'Move emails from' in response.data
            assert b'generated-rule-text' in response.data
            assert b'85%' in response.data

def test_multiple_rule_options_in_modal(self, client, user):
        """Test multiple rule options within the folder creation modal."""
            
                [
                    {'text': 'Move emails from boss@company.com', 'quality_score': 85},
                    {'text': 'Move emails with urgent subject', 'quality_score': 75},
                    {'text': 'Move emails from team members', 'quality_score': 70}
                ],
                {'total_generated': 3}
            )
            
            # Simulate multiple rule generation request
            })
            
            # Check that multiple rules are displayed
            assert b'Move emails from boss@company.com' in response.data
            assert b'Move emails with urgent subject' in response.data
            assert b'Move emails from team members' in response.data
            assert b'75%' in response.data
            assert b'70%' in response.data

folder = Folder.query.filter_by(name='Newsletters', user_id=user.id).first()

def test_folder_creation_with_ai_rule(self, client, user):
        """Test folder creation using AI-generated rule."""
            
                [{'text': "Move emails from 'newsletter@company.com' to this folder", 'quality_score': 90}],
            )
            
            # First, generate the rule
                'folder_name': 'Newsletters',
            })
            
            # Then create folder with the generated rule
            # We need to extract the rule from the response and use it
            soup = BeautifulSoup(response.data, 'html.parser')
            rule_text = soup.find(id='generated-rule-text').text.strip()
            
            response = client.post('/api/folders', data={
                'name': 'Newsletters',
                'rule_text': rule_text,
                'priority': '0'
            })
            
            assert response.status_code == 201
            # Check that folder was created
            folder = Folder.query.filter_by(name='Newsletters', user_id=user.id).first()
            assert folder is not None
            assert folder.rule_text == rule_text

def test_rule_quality_assessment(self, client, user):
        """Test rule quality assessment functionality."""
            
            mock_ai_service.assess_rule_quality.return_value = {
                'score': 75,
                'grade': 'good',
                'feedback': 'Good rule with room for improvement',
                'assessed_at': '2023-01-01T00:00:00'
            }
            
            response = client.post('/api/folders/assess-rule', data={
                'rule_text': 'Move emails from boss@company.com to this folder',
                'folder_type': 'destination'
            })
            
            # Check that quality assessment is displayed

def test_error_handling_in_modal(self, client, user):
        """Test error handling within the modal."""
            
        # Test with invalid inputs
        response = client.post('/api/folders/generate-rule', data={
            'folder_type': 'destination',
            'rule_type': 'multiple'
        })
        
        # Check that error is displayed in modal format
        assert b'Folder name is required' in response.data

def test_fallback_rule_generation(self, client, user):
        """Test fallback rule generation when AI service fails."""
            
            # Mock AI service failure
            mock_ai_service.generate_multiple_rules.return_value = (None, {'error': 'Service unavailable'})
            mock_ai_service.get_fallback_rule.return_value = 'Move emails containing "Work" to this folder'
            
            })
            
            # Check that fallback rule is displayed
            assert b'Move emails containing "Work" to this folder' in response.data
            assert b'AI service unavailable' in response.data

def test_cache_usage_indicator(self, client, user):
        """Test that cache usage is properly indicated."""
            
        # Create a cache entry
        from datetime import datetime
        cache_entry = AIRuleCache(
            folder_name='Work',
            folder_type='destination',
            rule_text='Cached rule',
            rule_metadata={'quality_score': 90},
            cache_key='test-key',
            expires_at=datetime(2023, 12, 31, 23, 59, 59),  # Future expiration
            is_active=True
        )
        db.session.add(cache_entry)
        
            # Make AI service return different rule to verify cache is used
            mock_ai_service.generate_single_rule.return_value = (
                'New rule',
                {'quality_score': 95}
            )
            
                'rule_type': 'single'
            })
            
            # Check that cached rule is used
            assert b'Using cached rule' in response.data
            assert b'Cached rule' in response.data
            # New rule should not appear
            assert b'New rule' not in response.data

def test_keyboard_navigation_support(self, client, user):
        """Test that keyboard navigation is supported."""
            
        # Check that buttons have proper ARIA labels
        assert b'aria-label' in response.data
        assert b'Generate AI-powered email rule' in response.data
        assert b'Generate multiple AI-powered email rule options' in response.data

def test_screen_reader_support(self, client, user):
        """Test that screen reader support is implemented."""
            
                "Move emails from 'boss@company.com' to this folder",
                {'quality_score': 85, 'model_used': 'test-model'}
            )
            
            })
            
            # Check that screen reader support is present
            assert b'role="status"' in response.data
            assert b'aria-live="polite"' in response.data
            assert b'sr-only' in response.data

def test_loading_states(self, client, user):
        """Test that loading states are properly handled."""
            
        # Check that loading states are configured
        assert b'data-loading-disable' in response.data
        assert b'data-loading-class' in response.data
        assert b'loading-spinner' in response.data

class TestAIRuleUserFlow:
    """Test cases for the complete AI rule generation user flow."""
    
    @pytest.fixture
        
            yield app
            
            )
            
    def test_folder_creation_modal_with_ai_controls(self, client, user):
            
    def test_ai_rule_generation_in_modal(self, client, user):
            
            )
            
            })
            
    def test_multiple_rule_options_in_modal(self, client, user):
            
                [
                ],
            )
            
            })
            
    def test_folder_creation_with_ai_rule(self, client, user):
            
            )
            
            })
            
            })
            
    def test_rule_quality_assessment(self, client, user):
            
            }
            
            })
            
    def test_error_handling_in_modal(self, client, user):
            
        })
        
    def test_fallback_rule_generation(self, client, user):
            
            })
            
    def test_cache_usage_indicator(self, client, user):
            
        )
        
            )
            
            })
            
    def test_keyboard_navigation_support(self, client, user):
            
    def test_screen_reader_support(self, client, user):
            
            )
            
            })
            
    def test_loading_states(self, client, user):
            
configure_button = page.locator("button:has-text('Configure IMAP')")

folder_cards_before = page.locator(".card.bg-base-100.shadow-xl")

folder_cards_after_delete = page.locator(".card.bg-base-100.shadow-xl")

def test_folder_deletion_and_resync_flow(playwright: playwright.sync_api.Playwright):
    """
    Test the complete folder deletion and resync flow:
    1. Navigate to localhost:5001
    2. Create a new user
    3. Configure IMAP with specified details
    4. Sync folders and verify they exist
    5. Delete all folders
    6. Verify folders are deleted
    7. Resync folders
    8. Verify folders reappear
    """
    
    # Launch browser
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    
    try:
        # Generate unique data for this test run
        random_number = random.randint(1000, 9999)
        test_email = f"testuser{random_number}@example.com"
        test_username = f"user{random_number}"
        
        # Step 1: Navigate to localhost:5000 (login page)
        page.goto("http://localhost:5001")
        
        # Wait for page to load
        page.wait_for_load_state("networkidle")

        # Step 2: Navigate to signup page
        # Click the "Sign up" link
        page.click("a:has-text('Sign up')")
        
        # Wait for navigation to complete

        # Step 3: Create a new user
        # Fill out signup form
        page.fill("input[name='first_name']", "Test")
        page.fill("input[name='last_name']", "User")
        page.fill("input[name='email']", test_email)
        page.fill("input[name='password']", "TestPassword123")
        page.fill("input[name='confirm_password']", "TestPassword123")
        page.check("input[name='terms']")
        
        # Submit signup form
        page.click("button:has-text('Create Account')")
        
        # Step 4: Configure IMAP
        # Wait for and click the "Configure IMAP" button
        configure_button = page.locator("button:has-text('Configure IMAP')")
        configure_button.click()
        
        # Wait for modal to appear
        page.wait_for_selector("#imap-modal")
        
        # Fill out IMAP configuration form
        page.fill("input[name='server']", "localhost")
        page.fill("input[name='port']", "5143")
        page.fill("input[name='username']", "user1@example.com")
        page.fill("input[name='password']", "password1")
        
        # Uncheck SSL checkbox
        page.uncheck("input[name='use_ssl']")
        
        # Submit IMAP configuration form
        page.click("button[type='submit']")
        
        # Wait for connection test to complete
        page.wait_for_selector("h3:has-text('Configure IMAP Folders')")
        
        # Step 5: Click "Save and Continue" on the final modal
        page.click("button:has-text('Save and Continue')")
        
        # Wait for modal to close and navigation to complete
        
        # Wait for modal to appear and folders to sync
        page.wait_for_selector("#folders-list", timeout=10000)
        
        # Wait for at least 3 folder cards to be visible (based on existing test)
        page.wait_for_function("document.querySelectorAll('.card.bg-base-100.shadow-xl').length >= 3", timeout=15000)
        
        # Get the count of folder cards
        folder_cards_before = page.locator(".card.bg-base-100.shadow-xl")
        count_before = folder_cards_before.count()
        print(f"Found {count_before} folder cards before deletion")
        
        # Verify we have at least 3 folder cards
        assert count_before >= 3, f"Expected at least 3 folder cards, but found {count_before}"
        
        # Step 6: Delete all folders
        # Find all delete buttons for folders
        delete_buttons = page.locator(".delete-button")
        
        print(f"Found {delete_buttons.count()} folders to delete")
        
        # Click each delete button to delete all folders
        while delete_buttons.count() > 0:
            # Get the delete button and click it
            delete_button = delete_buttons.nth(0)
            delete_button.click()
            
            # Wait for the deletion to process and UI to update
            page.wait_for_timeout(1000)  # Small delay to allow processing
            page.on('dialog', lambda dialog:  dialog.accept())
            # Wait for the folder card to be removed from the DOM
            page.wait_for_load_state("networkidle")
            delete_buttons = page.locator(".delete-button")
        
        # Wait for final state to settle
        
        # Step 7: Verify folders are deleted
        folder_cards_after_delete = page.locator(".card.bg-base-100.shadow-xl")
        count_after_delete = folder_cards_after_delete.count()
        print(f"Found {count_after_delete} folder cards after deletion")
        
        # Verify all folders are deleted
        assert count_after_delete == 0, f"Expected 0 folder cards after deletion, but found {count_after_delete}"
        
    finally:
        # Close browser
        browser.close()

def test_full_user_flow(playwright: playwright.sync_api.Playwright):
    """
    Test the complete user flow:
    2. Go to signup page from login
    3. Create a new user
    4. Configure IMAP with specified details
    5. Sync folders
    6. Verify at least 3 folder cards are displayed
    """
    
    try:
        import random
        
        # Step 3: Configure IMAP
        
        page.wait_for_selector("#folders-list .card", timeout=10000)
        
        # Step 6: Verify at least 3 folder cards are displayed
        folder_cards = page.locator(".card.bg-base-100.shadow-xl")
        
        # Wait for at least 3 folder cards to be visible
        
        count = folder_cards.count()
        print(f"Found {count} folder cards")
        
        assert count >= 3, f"Expected at least 3 folder cards, but found {count}"
        
        # Verify each folder card has the expected structure
        for i in range(min(3, count)):
            card = folder_cards.nth(i)
            expect(card).to_be_visible()
            
    finally:

response = authenticated_client.get(f'/api/folders/{folder.id}/pending-emails')

def test_get_pending_emails_success(self, app, mock_user, authenticated_client):
        """Test get_pending_emails endpoint successfully."""
            # Create a folder
            folder = Folder(
                user_id=mock_user.id,
                name='Test Folder',
                rule_text='Test rule'
            )
            db.session.add(folder)
            
            # Create some pending email records
            pending_emails = [
                ProcessedEmail(
                    user_id=mock_user.id,
                    folder_id=folder.id,
                    folder_name='Test Folder',
                    email_uid='123',
                    is_processed=False
                ),
                    email_uid='456',
                )
            ]
            db.session.bulk_save_objects(pending_emails)
            
            # Mock IMAP service to return email headers
            with patch('app.imap_service.IMAPService') as mock_imap_service:
                mock_imap_instance = mock_imap_service.return_value
                mock_imap_instance.get_email_headers.side_effect = [
                    {
                        'subject': 'Test Subject 1',
                        'date': '2023-01-01T12:00:00',
                        'from': 'sender1@example.com',
                        'to': 'recipient@example.com',
                        'message_id': '<msg1@example.com>'
                    },
                    {
                        'subject': 'Test Subject 2',
                        'date': '2023-01-02T12:00:00',
                        'from': 'sender2@example.com',
                        'message_id': '<msg2@example.com>'
                    }
                ]
                
                response = authenticated_client.get(f'/api/folders/{folder.id}/pending-emails')
                
                assert response.status_code == 200
                # The response should be HTML with the pending emails dialog
                response_text = response.get_data(as_text=True)
                # Check that the response contains the expected content
                assert 'Pending Emails in Test Folder' in response_text
                assert 'Total Emails' in response_text
                assert 'Pending' in response_text
                assert 'Processed' in response_text

response = authenticated_client.get('/api/folders/999/pending-emails')

def test_get_pending_emails_folder_not_found(self, app, mock_user, authenticated_client):
        """Test get_pending_emails endpoint with non-existent folder."""
            response = authenticated_client.get('/api/folders/999/pending-emails')
            
            assert response.status_code == 404
            assert 'Folder not found' in response.get_json()['error']

def test_mark_email_processed_success(self, app, mock_user, authenticated_client):
        """Test mark_email_processed endpoint successfully."""
            )
            
            # Create a pending email record
            pending_email = ProcessedEmail(
                folder_id=folder.id,
                folder_name='Test Folder',
                email_uid='123',
                is_processed=False
            )
            db.session.add(pending_email)
            
            response = authenticated_client.post(f'/api/folders/{folder.id}/emails/123/process')
            
            # The response should be HTML with the updated dialog
            assert 'has been marked as processed successfully' in response.get_data(as_text=True)
            
            # Verify the email is marked as processed
            updated_email = ProcessedEmail.query.filter_by(
                email_uid='123'
            ).first()
            assert updated_email.is_processed is True

response = authenticated_client.post('/api/folders/999/emails/123/process')

login_response = response = authenticated_client.post('/api/folders/999/emails/123/process')

def test_mark_email_processed_folder_not_found(self, app, mock_user, authenticated_client):
        """Test mark_email_processed endpoint with non-existent folder."""
            # Login and make request
            login_response = response = authenticated_client.post('/api/folders/999/emails/123/process')
            
response = authenticated_client.post(f'/api/folders/{folder.id}/sync-emails')

def test_sync_folder_emails_success(self, app, mock_user, authenticated_client):
        """Test sync_folder_emails endpoint successfully."""
            )
            
            # Mock IMAP service to return email UIDs
            with patch('app.routes.emails.IMAPService') as mock_imap_service:
                mock_imap_instance.get_folder_email_uids.return_value = ['123', '456', '789']
                
                # Set up the user's IMAP config
                mock_user.imap_config = {
                    'server': 'localhost',
                    'port': 5143,
                    'username': 'user1@example.com',
                    'password': 'password1',
                    'use_ssl': False
                }
                db.session.commit()
                
                # Login and make request
                response = authenticated_client.post(f'/api/folders/{folder.id}/sync-emails')
                
                data = response.get_json()
                print(data)
                assert data['success'] is True
                assert 'Synced 3 new emails' in data['message']
                assert data['pending_count'] == 3
                assert data['total_count'] == 3
                
                # Verify records were created
                records = ProcessedEmail.query.filter_by(
                    folder_name='Test Folder'
                ).all()
                assert len(records) == 3
                
                ).all()

def test_sync_folder_emails_no_emails(self, app, mock_user, authenticated_client):
        """Test sync_folder_emails endpoint with no emails found."""
            )
            
            # Mock IMAP service to return no email UIDs
                mock_imap_instance.get_folder_email_uids.return_value = []
                
                assert 'No emails found in folder' in response.get_json()['error']

response = authenticated_client.post('/api/folders/999/sync-emails')

login_response = response = authenticated_client.post('/api/folders/999/sync-emails')

def test_sync_folder_emails_folder_not_found(self, app, mock_user, authenticated_client):
        """Test sync_folder_emails endpoint with non-existent folder."""
            login_response = response = authenticated_client.post('/api/folders/999/sync-emails')
            
response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={

login_response = response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={

def test_process_folder_emails_success(self, app, mock_user, authenticated_client):
        """Test process_folder_emails endpoint successfully."""
            )
            
                ),
                )
            ]
            
            login_response = response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={
                'email_uids': ['123', '456']
            })
            
            data = response.get_json()
            assert data['success'] is True
            assert 'Processed 2 emails' in data['message']
            assert data['pending_count'] == 0
            
            # Verify emails are marked as processed
            processed_emails = ProcessedEmail.query.filter_by(
                is_processed=True
            ).all()
            assert len(processed_emails) == 2

response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={})

login_response = response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={})

def test_process_folder_emails_no_uids(self, app, mock_user, authenticated_client):
        """Test process_folder_emails endpoint with no email UIDs provided."""
            )
            
            login_response = response = authenticated_client.post(f'/api/folders/{folder.id}/process-emails', data={})
            
            assert response.status_code == 400
            assert 'No email UIDs provided' in response.get_json()['error']

response = authenticated_client.post('/api/folders/999/process-emails', data={

login_response = response = authenticated_client.post('/api/folders/999/process-emails', data={

def test_process_folder_emails_folder_not_found(self, app, mock_user, authenticated_client):
        """Test process_folder_emails endpoint with non-existent folder."""
            login_response = response = authenticated_client.post('/api/folders/999/process-emails', data={
            })
            
class TestProcessedEmailsRoutes:
    def test_get_pending_emails_success(self, app, mock_user, authenticated_client):
            )
            
                ),
                )
            ]
            
                    {
                    },
                    {
                    }
                ]
                
    def test_get_pending_emails_folder_not_found(self, app, mock_user, authenticated_client):
            
    def test_mark_email_processed_success(self, app, mock_user, authenticated_client):
            )
            
            )
            
            ).first()
    
    def test_mark_email_processed_folder_not_found(self, app, mock_user, authenticated_client):
            
    def test_sync_folder_emails_success(self, app, mock_user, authenticated_client):
            )
            
                }
                
                ).all()
                
                ).all()
    
    def test_sync_folder_emails_no_emails(self, app, mock_user, authenticated_client):
            )
            
    def test_sync_folder_emails_folder_not_found(self, app, mock_user, authenticated_client):
            
    def test_process_folder_emails_success(self, app, mock_user, authenticated_client):
            )
            
                ),
                )
            ]
            
            })
            
            ).all()
    
    def test_process_folder_emails_no_uids(self, app, mock_user, authenticated_client):
            )
            
    def test_process_folder_emails_folder_not_found(self, app, mock_user, authenticated_client):
            })
            
            )
            # Refresh the user to ensure it's attached to the session
            db.session.refresh(user)

    def authenticated_client(self, client, user):
        """Create a test client with authenticated user."""
            sess['_user_id'] = str(user.id)
            sess['_fresh'] = True
        return client

response = authenticated_client.post('/api/folders/generate-rule', data={

def test_generate_rule_success(self, authenticated_client, user):
        """Test successful rule generation."""
            )
            
            response = authenticated_client.post('/api/folders/generate-rule', data={
                'name': 'Work',
            })
            
            # Check that the response contains HTML

def test_generate_rule_missing_folder_name(self, authenticated_client, user):
        """Test rule generation with missing folder name."""
        response = authenticated_client.post('/api/folders/generate-rule', data={
        })
        
        # Should return error in HTML format

def test_generate_rule_invalid_folder_type(self, authenticated_client, user):
        """Test rule generation with invalid folder type."""
            'name': 'Work',
            'folder_type': 'invalid',
        })
        
        assert b'Invalid folder type' in response.data

def test_generate_rule_multiple_options(self, authenticated_client, user):
        """Test multiple rule options generation."""
                [
                    {'text': 'Rule 1', 'quality_score': 85},
                    {'text': 'Rule 2', 'quality_score': 75}
                ],
                {'total_generated': 2}
            )
            
            })
            
            assert b'Rule 1' in response.data
            assert b'Rule 2' in response.data

def test_generate_rule_ai_service_failure(self, authenticated_client, user):
        """Test rule generation when AI service fails."""
            mock_ai_service.get_fallback_rule.return_value = 'Fallback rule'
            
            })
            
            # Should return fallback rule
            assert b'Fallback rule' in response.data

response = authenticated_client.post('/api/folders/assess-rule', data={

def test_assess_rule_success(self, authenticated_client, user):
        """Test successful rule assessment."""
                'score': 85,
                'feedback': 'Good rule with room for improvement'
            }
            
            response = authenticated_client.post('/api/folders/assess-rule', data={
            })
            
def test_assess_rule_missing_inputs(self, authenticated_client, user):
        """Test rule assessment with missing inputs."""
        response = authenticated_client.post('/api/folders/assess-rule', data={
            'folder_name': 'Work',
            'folder_type': 'destination'
        })
        
        assert b'Rule text is required' in response.data

response1 = authenticated_client.post('/api/folders/generate-rule', data={

response2 = authenticated_client.post('/api/folders/generate-rule', data={

def test_cache_functionality(self, authenticated_client, user):
        """Test rule caching functionality."""
                [{'text': "Cached rule", 'quality_score': 90}],
            )
            
            # First request - should generate new rule
            response1 = authenticated_client.post('/api/folders/generate-rule', data={
            })
            
            assert response1.status_code == 200
            assert b'Cached rule' in response1.data
            
            # Verify cache entry was created
            cache_entry = AIRuleCache.query.filter_by(
                user_id=user.id,
                folder_name='Work',
                folder_type='destination'
            ).first()
            assert cache_entry is not None
            assert cache_entry.rule_text == 'Cached rule'
            
            # Second request - should use cached rule
            with patch('app.routes.folders.ai_service') as mock_ai_service:
                response2 = authenticated_client.post('/api/folders/generate-rule', data={
                    'name': 'Work',
                    'folder_type': 'destination',
                    'rule_type': 'multiple'
                })
                
                assert response2.status_code == 200
                assert b'Using cached rule' in response2.data

def test_cache_expiration(self, authenticated_client, user):
        """Test cache expiration functionality."""
                [{'text': "Expired rule", 'quality_score': 90}],
            )
            
            # Create expired cache entry
            from datetime import datetime, timedelta
            expired_entry = AIRuleCache(
                folder_type='destination',
                rule_text='Expired rule',
                rule_metadata={'quality_score': 90},
                cache_key='test-key',
                expires_at=datetime.utcnow() - timedelta(hours=1),
                is_active=True
            )
            db.session.add(expired_entry)
            
            # Request should generate new rule despite cache entry
            })
            
            # Should not show cached message
            assert b'Using cached rule' not in response.data

def test_unauthorized_access(self, client):
        """Test unauthorized access to AI rule endpoints."""
        })
        
        # Should be redirected to login
        assert response.status_code == 302

def test_database_error_handling(self, authenticated_client, user):
        """Test handling of database errors."""
        with patch('app.routes.folders.db.session.commit') as mock_commit:
            # Mock database commit failure
            mock_commit.side_effect = Exception("Database error")
            
                mock_ai_service.generate_multiple_rules.return_value = (
                    [{'text': "Test rule", 'quality_score': 85}],
                    {'total_generated': 1}
                )
                
                response = authenticated_client.post('/api/folders/generate-rule', data={
                })
                
                # Should return error message
                assert b'An unexpected error occurred' in response.data

class TestAIRuleEndpoints:
    """Test cases for AI rule generation API endpoints."""
    
            yield app
            
            )
            
    def test_generate_rule_success(self, authenticated_client, user):
            )
            
            })
            
    def test_generate_rule_missing_folder_name(self, authenticated_client, user):
        })
        
    def test_generate_rule_invalid_folder_type(self, authenticated_client, user):
        })
        
    def test_generate_rule_multiple_options(self, authenticated_client, user):
                [
                ],
            )
            
            })
            
    def test_generate_rule_ai_service_failure(self, authenticated_client, user):
            
            })
            
    def test_assess_rule_success(self, authenticated_client, user):
            }
            
            })
            
    def test_assess_rule_missing_inputs(self, authenticated_client, user):
        })
        
    def test_cache_functionality(self, authenticated_client, user):
            )
            
            })
            
            ).first()
            
                })
                
    def test_cache_expiration(self, authenticated_client, user):
            )
            
            )
            
            })
            
    def test_unauthorized_access(self, client):
        })
        
    def test_database_error_handling(self, authenticated_client, user):
            
                )
                
                })
                
def test_imap_config_modal(self, authenticated_client):
        response = authenticated_client.get('/api/imap/config')
        assert b'Configure IMAP Connection' in response.data

def test_imap_connection_test_success(self, authenticated_client, app):
        response = authenticated_client.post('/api/imap/test', data={
            'server': 'localhost',
            'port': '5143',
            'username': 'user1@example.com',
            'password': 'password1',
            'use_ssl': False
        })
        print(response.data)
        
        # After successful connection, it should show the folder selection modal
        assert b'Configure IMAP Folders' in response.data

response = authenticated_client.post('/api/imap/sync-selected', data={})

def test_imap_sync_selected_folders_no_config(self, authenticated_client):
        """Test sync-selected fails when no IMAP config is set"""
        response = authenticated_client.post('/api/imap/sync-selected', data={})
        assert response.status_code == 400
        assert b'No IMAP configuration found' in response.data

def test_imap_sync_selected_folders_no_selection(self, authenticated_client, mock_user):
        """Test sync-selected fails when no folders are selected"""
        # Set up IMAP config
        mock_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
        
        assert b'No folders selected' in response.data

initial_folders = Folder.query.filter_by(user_id=mock_user.id).all()

response = authenticated_client.post('/api/imap/sync-selected', data=form_data)

new_folders = Folder.query.filter_by(user_id=mock_user.id).all()

receipts_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()

marketing_folder = Folder.query.filter_by(user_id=mock_user.id, name='Marketing').first()

def test_imap_sync_selected_folders_with_real_folders(self, authenticated_client, mock_user):
        """Test sync-selected succeeds with real folders from the IMAP server"""
        
        # Get initial folder count
        initial_folders = Folder.query.filter_by(user_id=mock_user.id).all()
        initial_count = len(initial_folders)
        
        # Use real folder names from the IMAP server that we saw in the test output
        # Note: The endpoint strips leading/trailing spaces before storing
        form_data = {
            'folder_0': ' Receipts',
            'folder_type_0': 'destination',
            'folder_1': ' Marketing',
            'folder_type_1': 'tidy'
        }
        
        response = authenticated_client.post('/api/imap/sync-selected', data=form_data)
        
        # Should return 200 with proper headers
        assert response.headers.get('HX-Trigger') == 'close-modal, folder-list-invalidated'
        
        # Check that new folders were created
        new_folders = Folder.query.filter_by(user_id=mock_user.id).all()
        assert len(new_folders) > initial_count
        
        # Verify folder types were set correctly (names are stripped before storing)
        receipts_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()
        marketing_folder = Folder.query.filter_by(user_id=mock_user.id, name='Marketing').first()
        
        assert receipts_folder is not None
        assert receipts_folder.folder_type == 'destination'
        assert marketing_folder is not None
        assert marketing_folder.folder_type == 'tidy'

initial_count = Folder.query.filter_by(user_id=mock_user.id).count()

final_folders = Folder.query.filter_by(user_id=mock_user.id).all()

updated_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()

new_folder = Folder.query.filter_by(user_id=mock_user.id, name='Personal').first()

def test_imap_sync_selected_folders_existing_folders(self, authenticated_client, mock_user):
        """Test sync-selected updates existing folders instead of creating duplicates"""
        
        # Clean up any existing folders first
        Folder.query.filter_by(user_id=mock_user.id).delete()
        
        # Create an existing folder (without leading spaces since they get stripped)
        existing_folder = Folder(
            user_id=mock_user.id,
            name='Receipts',  # This is how it will be stored after stripping
            total_count=5,
            pending_count=2
        )
        db.session.add(existing_folder)
        
        initial_count = Folder.query.filter_by(user_id=mock_user.id).count()
        
        # Select the existing folder and a new one
            'folder_0': ' Receipts',  # IMAP returns with space, but gets stripped
            'folder_type_0': 'tidy',  # Different type to test update
            'folder_1': ' Personal',
            'folder_type_1': 'destination'
        }
        
        # Check that we have exactly one more folder (the new one)
        final_folders = Folder.query.filter_by(user_id=mock_user.id).all()
        assert len(final_folders) == initial_count + 1
        
        # Check that existing folder type was updated
        updated_folder = Folder.query.filter_by(user_id=mock_user.id, name='Receipts').first()
        assert updated_folder is not None
        assert updated_folder.folder_type == 'tidy'
        
        # Check that new folder was created (name gets stripped)
        new_folder = Folder.query.filter_by(user_id=mock_user.id, name='Personal').first()
        assert new_folder is not None
        assert new_folder.folder_type == 'destination'

work_folder = Folder.query.filter_by(user_id=mock_user.id, name='Work').first()

folder = Folder.query.filter_by(user_id=mock_user.id, name=folder_name).first()

def test_imap_sync_selected_folders_special_folders_skipped(self, authenticated_client, mock_user):
        """Test sync-selected skips special folders like sent, drafts, spam, trash"""
        
        # Try to select special folders along with real folders
            'folder_0': 'Sent',
            'folder_1': 'Drafts',
            'folder_type_1': 'tidy',
            'folder_2': 'Spam',
            'folder_type_2': 'destination',
            'folder_3': 'Trash',
            'folder_type_3': 'tidy',
            'folder_4': ' Work',
            'folder_type_4': 'destination'
        }
        
        # Check that only Work folder was created (special folders should be skipped)
        
        # Check that Work folder exists (name gets stripped)
        work_folder = Folder.query.filter_by(user_id=mock_user.id, name='Work').first()
        assert work_folder is not None
        assert work_folder.folder_type == 'destination'
        
        # Check that special folders don't exist
        special_folders = ['Sent', 'Drafts', 'Spam', 'Trash']
        for folder_name in special_folders:
            folder = Folder.query.filter_by(user_id=mock_user.id, name=folder_name).first()
            assert folder is None, f"Special folder {folder_name} should not have been created"

class TestIMAPRoutes:
    def test_imap_config_modal(self, authenticated_client):
    
    def test_imap_connection_test_success(self, authenticated_client, app):
        })
        
    def test_imap_sync_selected_folders_no_config(self, authenticated_client):
    
    def test_imap_sync_selected_folders_no_selection(self, authenticated_client, mock_user):
        
    def test_imap_sync_selected_folders_with_real_folders(self, authenticated_client, mock_user):
        
        }
        
    def test_imap_sync_selected_folders_existing_folders(self, authenticated_client, mock_user):
        
        )
        
        }
        
    def test_imap_sync_selected_folders_special_folders_skipped(self, authenticated_client, mock_user):
        
        }
        
response = authenticated_client.delete(f'/api/folders/{folder.id}')

def test_delete_folder_with_emails(self, app, mock_user, authenticated_client):
        """Test that deleting a folder also deletes its associated emails."""
            )
            
            # Create some email records associated with this folder
            email_records = [
                ),
                )
            ]
            db.session.bulk_save_objects(email_records)
            
            # Verify emails were created
            assert ProcessedEmail.query.count() == 2
            
            # Delete the folder
            response = authenticated_client.delete(f'/api/folders/{folder.id}')
            
            # Verify the response is successful
            
            # Verify the folder is deleted
            deleted_folder = Folder.query.filter_by(id=folder.id).first()
            assert deleted_folder is None
            
            # Verify the associated emails are also deleted (cascade delete)
            assert ProcessedEmail.query.count() == 0

def test_delete_folder_with_no_emails(self, app, mock_user, authenticated_client):
        """Test that deleting a folder with no associated emails works normally."""
            )
            
            # Verify no emails are associated with this folder
            assert ProcessedEmail.query.filter_by(folder_id=folder.id).count() == 0
            
class TestFolderDeletion:
    def test_delete_folder_with_emails(self, app, mock_user, authenticated_client):
            )
            
                ),
                )
            ]
            
    def test_delete_folder_with_no_emails(self, app, mock_user, authenticated_client):
            )
            
def app():
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

        db.create_all()
        yield app
        db.drop_all()

def runner(app):
    """A test runner for the app."""
    return app.test_cli_runner()

def test_login_page_loads(self, client):
        """Test that the login page loads successfully."""
        response = client.get('/auth/login')
        assert b'Login' in response.data
        assert b'Email' in response.data
        assert b'Password' in response.data

def test_signup_page_loads(self, client):
        """Test that the signup page loads successfully."""
        response = client.get('/auth/signup')
        assert b'Create Account' in response.data
        assert b'First Name' in response.data
        assert b'Last Name' in response.data

def test_user_registration_success(self, client):
        """Test successful user registration."""
        response = client.post('/auth/signup', data={
            'first_name': 'John',
            'last_name': 'Doe',
            'email': 'john@example.com',
            'password': 'Password123',
            'confirm_password': 'Password123'
        }, follow_redirects=True)
        
        assert response.status_code == 200  # Shows the signup page with success message
        
        # Verify user was created in database
        user = User.query.filter_by(email='john@example.com').first()
        assert user is not None
        assert user.first_name == 'John'
        assert user.last_name == 'Doe'
        assert user.check_password('Password123')

def test_user_registration_duplicate_email(self, client):
        """Test registration with duplicate email."""
        # Create first user
        client.post('/auth/signup', data={
        })
        
        # Try to create user with same email
            'first_name': 'Jane',
            'last_name': 'Smith',
            'password': 'Password456',
            'confirm_password': 'Password456'
        })
        
        assert response.status_code == 302  # Redirects to login page with error
        # Check that the user was redirected and the flash message is in the session
        assert response.location == '/'

def test_user_registration_validation_errors(self, client):
        """Test user registration validation errors."""
            'first_name': '',  # Empty first name
            'email': 'invalid-email',  # Invalid email
            'password': 'short',  # Too short
            'confirm_password': 'nomatch'  # Doesn't match
        })
        
        assert b'First name is required' in response.data
        assert b'Please enter a valid email address' in response.data
        assert b'Password must be at least 8 characters' in response.data
        assert b'Passwords do not match' in response.data

def test_user_login_success(self, client):
        """Test successful user login."""
        # Create user first
        })
        
        # Login
        response = client.post('/auth/login', data={
            'password': 'Password123'
        
        assert b'Email Organizer' in response.data
        assert b'John Doe' in response.data

def test_user_login_invalid_credentials(self, client):
        """Test login with invalid credentials."""
        })
        
        # Login with wrong password
            'password': 'WrongPassword'
        })
        
def test_user_login_nonexistent_user(self, client):
        """Test login with non-existent user."""
            'email': 'nonexistent@example.com',
        })
        
        assert b'Invalid email or password' in response.data

def test_user_login_validation_errors(self, client):
        """Test login validation errors."""
            'email': '',  # Empty email
            'password': ''  # Empty password
        })
        
        assert b'Email is required' in response.data
        # The password validation is not working as expected in the current implementation
        # This test needs to be updated to match the actual behavior

def test_logout(self, client):
        """Test user logout."""
        # Create and login user
        })
        
        # Login
        client.post('/auth/login', data={
        })
        
        # Logout
        response = client.get('/auth/logout', follow_redirects=True)
        
        assert b'Sign in to your account' in response.data

def test_protected_page_requires_login(self, client):
        """Test that protected pages require login."""
        response = client.get('/')
        
        # Should redirect to login
        assert '/auth/login' in response.location

def test_authenticated_user_cannot_access_auth_pages(self, client):
        """Test that authenticated users cannot access auth pages."""
        })
        
        # Try to access login page
        assert response.status_code == 302  # Should redirect to home
        
        # Try to access signup page

def test_password_hashing(self, client):
        """Test that passwords are properly hashed."""
        })
        
        assert user.password_hash is not None
        assert user.password_hash != b'Password123'  # Should be hashed
        assert user.check_password('Password123')  # Should verify correctly
        assert not user.check_password('WrongPassword')  # Should reject wrong password

def test_user_password_strength_requirements(self, client):
        """Test password strength requirements."""
        # Test various weak passwords
        weak_passwords = [
            'short',  # Too short
            'alllowercase',  # No uppercase
            'ALLUPPERCASE',  # No lowercase
            '12345678',  # No letters
            'NoNumbers',  # No numbers
        ]
        
        for password in weak_passwords:
            response = client.post('/auth/signup', data={
                'first_name': 'John',
                'last_name': 'Doe',
                'email': f'john{password}@example.com',
                'password': password,
                'confirm_password': password
            })
            
            assert b'Password must contain at least one uppercase letter' in response.data or \
                   b'Password must contain at least one lowercase letter' in response.data or \
                   b'Password must contain at least one digit' in response.data or \
                   b'Password must be at least 8 characters' in response.data

def test_user_model_methods(self, client):
        """Test User model methods."""
        # Create user
        user = User(
            first_name='John',
            last_name='Doe',
            email='john@example.com'
        )
        user.set_password('Password123')
        
        db.session.add(user)
        
        # Test check_password method
        assert not user.check_password('WrongPassword')
        
        # Test __repr__ method
        assert repr(user) == f'<User {user.first_name} {user.last_name} ({user.email})>'

class TestAuthentication:
    """Test authentication functionality."""

    def test_login_page_loads(self, client):

    def test_signup_page_loads(self, client):

    def test_user_registration_success(self, client):
        
    def test_user_registration_duplicate_email(self, client):
        })
        
        })
        
    def test_user_registration_validation_errors(self, client):
        })
        
    def test_user_login_success(self, client):
        })
        
        # Login
        
    def test_user_login_invalid_credentials(self, client):
        })
        
        })
        
    def test_user_login_nonexistent_user(self, client):
        })
        
    def test_user_login_validation_errors(self, client):
        })
        
    def test_logout(self, client):
        })
        
        # Login
        })
        
        # Logout
        
    def test_protected_page_requires_login(self, client):
        
    def test_authenticated_user_cannot_access_auth_pages(self, client):
        })
        
    def test_password_hashing(self, client):
        })
        
    def test_user_password_strength_requirements(self, client):
        ]
        
            })
            
    def test_user_model_methods(self, client):
        )
        
def test_index_route(client, app, mock_user):
    """Test the index route requires authentication."""
    response = client.get('/')
    # Should redirect to login page
    assert response.status_code == 302
    assert '/login' in response.location

def test_index_route_authenticated(authenticated_client, app, mock_user):
    """Test the index route works for authenticated users."""
    response = authenticated_client.get('/')
    assert response.status_code == 200
    # Check if the page contains expected elements
    assert b'Email Organizer' in response.data
    assert b'Folders' in response.data
    assert b'Test User' in response.data  # Should show user's name

created_folder = Folder.query.filter_by(name='Test Folder').first()

def test_add_folder_route(authenticated_client, mock_user):
    """Test the add folder API endpoint."""
    # Get initial count of folders for the user
    initial_folder_count = Folder.query.count()
    
    # Send form data (URL encoded) instead of JSON
    response = authenticated_client.post('/api/folders',
                           data={'name': 'Test Folder', 'rule_text': 'Test rule something ok yes'},
                           content_type='application/x-www-form-urlencoded')
    
    # Verify the response status is 201 Created
    assert response.status_code == 201
    
    # Verify that the number of folders has increased
    final_folder_count = Folder.query.count()
    assert final_folder_count > initial_folder_count
    
    # Verify the folder belongs to the authenticated user
    created_folder = Folder.query.filter_by(name='Test Folder').first()
    assert created_folder.user_id == mock_user.id

def test_add_folder_validation_failure_empty_name(authenticated_client, mock_user):
    """Test validation failure when folder name is empty."""
                           data={'name': '', 'rule_text': 'Test rule something ok yes'},
    
    soup = BeautifulSoup(response.data, 'html.parser')
    # Check that the specific error message is present
    error_text = soup.find(string='Folder name is required')
    assert error_text is not None
    # Check that the input field has the error class
    name_input = soup.find('input', {'name': 'name'})
    assert name_input is not None
    assert 'input-error' in name_input.get('class', [])

error_text = soup.find(string='Folder name must be at least 3 characters')

def test_add_folder_validation_failure_short_name(authenticated_client, mock_user):
    """Test validation failure when folder name is too short."""
                           data={'name': 'ab', 'rule_text': 'Test rule something ok yes'},
    
    error_text = soup.find(string='Folder name must be at least 3 characters')

error_text = soup.find(string='Folder name must be less than 50 characters')

def test_add_folder_validation_failure_long_name(authenticated_client, mock_user):
    """Test validation failure when folder name is too long."""
    long_name = 'a' * 51
                           data={'name': long_name, 'rule_text': 'Test rule something ok yes'},
    
    error_text = soup.find(string='Folder name must be less than 50 characters')

def test_add_folder_validation_failure_empty_rule(authenticated_client, mock_user):
    """Test validation failure when rule text is empty."""
                           data={'name': 'Test Folder', 'rule_text': ''},
    
    error_text = soup.find(string='Rule text is required')

error_text = soup.find(string='Rule text must be at least 10 characters')

def test_add_folder_validation_failure_short_rule(authenticated_client, mock_user):
    """Test validation failure when rule text is too short."""
                           data={'name': 'Test Folder', 'rule_text': 'short'},
    
    error_text = soup.find(string='Rule text must be at least 10 characters')

error_text = soup.find(string='Rule text must be less than 200 characters')

def test_add_folder_validation_failure_long_rule(authenticated_client, mock_user):
    """Test validation failure when rule text is too long."""
    long_rule = 'a' * 201
                           data={'name': 'Test Folder', 'rule_text': long_rule},
    
    error_text = soup.find(string='Rule text must be less than 200 characters')

error_text1 = soup.find(string='Folder name must be at least 3 characters')

error_text2 = soup.find(string='Rule text must be at least 10 characters')

def test_add_folder_validation_multiple_errors(authenticated_client, mock_user):
    """Test validation failure with multiple errors."""
                           data={'name': 'ab', 'rule_text': 'short'},
    
    error_text1 = soup.find(string='Folder name must be at least 3 characters')
    error_text2 = soup.find(string='Rule text must be at least 10 characters')
    assert error_text1 is not None
    assert error_text2 is not None

response = authenticated_client.put(f'/api/folders/{mock_folder.id}',

def test_edit_folder_validation_failure_empty_name(authenticated_client, mock_folder):
    """Test validation failure when folder name is empty during edit."""
    response = authenticated_client.put(f'/api/folders/{mock_folder.id}',
    
def test_edit_folder_validation_failure_short_name(authenticated_client, mock_folder):
    """Test validation failure when folder name is too short during edit."""
    
def test_edit_folder_validation_failure_long_name(authenticated_client, mock_folder):
    """Test validation failure when folder name is too long during edit."""
    
def test_edit_folder_validation_failure_empty_rule(authenticated_client, mock_folder):
    """Test validation failure when rule text is empty during edit."""
    
def test_edit_folder_validation_failure_short_rule(authenticated_client, mock_folder):
    """Test validation failure when rule text is too short during edit."""
    
def test_edit_folder_validation_failure_long_rule(authenticated_client, mock_folder):
    """Test validation failure when rule text is too long during edit."""
    
def test_edit_folder_validation_multiple_errors(authenticated_client, mock_folder):
    """Test validation failure with multiple errors during edit."""
    
def test_add_folder_success_closes_dialog(authenticated_client, mock_user):
    """Test that successful folder creation triggers dialog close."""
    
    # Check for close-modal trigger in response headers
    assert 'HX-Trigger' in response.headers
    assert 'close-modal' in response.headers['HX-Trigger']

def test_edit_folder_success_closes_dialog(authenticated_client, mock_folder):
    """Test that successful folder update triggers dialog close."""
                           data={'name': 'Updated Folder', 'rule_text': 'Updated rule text'},
    
def test_add_folder_content_matches_submission(authenticated_client, mock_user):
    """Test that submitted folder content matches what was sent."""
    test_name = 'Test Folder Content'
    test_rule = 'Test rule content matching submission'
    test_priority = '1'
    
                           data={'name': test_name, 'rule_text': test_rule, 'priority': test_priority},
    
    # Verify the folder was created with correct content
    created_folder = Folder.query.filter_by(name=test_name).first()
    assert created_folder is not None
    assert created_folder.name == test_name.strip()
    assert created_folder.rule_text == test_rule.strip()
    assert created_folder.priority == int(test_priority)

updated_folder = Folder.query.filter_by(id=mock_folder.id).first()

def test_edit_folder_content_matches_submission(authenticated_client, mock_folder):
    """Test that updated folder content matches what was sent."""
    test_name = 'Updated Folder Content'
    test_rule = 'Updated rule content matching submission'
    test_priority = '-1'
    
    # Verify the folder was updated with correct content
    updated_folder = Folder.query.filter_by(id=mock_folder.id).first()
    assert updated_folder is not None
    assert updated_folder.name == test_name.strip()
    assert updated_folder.rule_text == test_rule.strip()
    assert updated_folder.priority == int(test_priority)

created_folder = Folder.query.filter_by(name='Test Folder With Whitespace').first()

def test_add_folder_content_whitespace_handling(authenticated_client, mock_user):
    """Test that whitespace is properly handled in submitted content."""
    test_name = '  Test Folder With Whitespace  '
    test_rule = '  Test rule with whitespace around it  '
    test_priority = '0'
    
    # Verify the folder was created with properly trimmed content
    created_folder = Folder.query.filter_by(name='Test Folder With Whitespace').first()
    assert created_folder.name == 'Test Folder With Whitespace'  # Should be trimmed
    assert created_folder.rule_text == 'Test rule with whitespace around it'  # Should be trimmed

def test_edit_folder_content_whitespace_handling(authenticated_client, mock_folder):
    """Test that whitespace is properly handled in updated content."""
    test_name = '  Updated Folder With Whitespace  '
    test_rule = '  Updated rule with whitespace around it  '
    
    # Verify the folder was updated with properly trimmed content
    assert updated_folder.name == 'Updated Folder With Whitespace'  # Should be trimmed
    assert updated_folder.rule_text == 'Updated rule with whitespace around it'  # Should be trimmed

response = authenticated_client.put(f'/api/folders/{mock_folder.id}/type', data={'folder_type': 'ignore'}, content_type='application/x-www-form-urlencoded')

response2 = authenticated_client.put(f'/api/folders/{mock_folder.id}/type', data={'folder_type': 'tidy'}, content_type='application/x-www-form-urlencoded')

updated_folder2 = Folder.query.filter_by(id=mock_folder.id).first()

def test_toggle_folder_organize_enabled(authenticated_client, mock_folder):
    """Test toggling the organize_enabled flag for a folder."""
    # Verify initial state is True (default)
    assert mock_folder.organize_enabled is True
    
    # Toggle the flag
    response = authenticated_client.put(f'/api/folders/{mock_folder.id}/type', data={'folder_type': 'ignore'}, content_type='application/x-www-form-urlencoded')
    
    # Should return 200 OK
    
    # Verify the folder was updated in the database
    assert updated_folder.folder_type == 'ignore'
    
    # Toggle back to make sure it works both ways
    response2 = authenticated_client.put(f'/api/folders/{mock_folder.id}/type', data={'folder_type': 'tidy'}, content_type='application/x-www-form-urlencoded')
    
    assert response2.status_code == 200
    
    # Verify the folder was updated in the database again
    updated_folder2 = Folder.query.filter_by(id=mock_folder.id).first()
    assert updated_folder2 is not None
    assert updated_folder2.folder_type == 'tidy'

def test_toggle_folder_organize_enabled_not_found(authenticated_client, mock_user):
    """Test toggling organize_enabled flag for a non-existent folder."""
    # Try to toggle a folder that doesn't exist
    response = authenticated_client.put('/api/folders/999/toggle')
    
    # Should return 404 Not Found

other_user = User(email='other@example.com', first_name='Other', last_name='User')

response = authenticated_client.put(f'/api/folders/{other_folder.id}/toggle')

def test_toggle_folder_organize_enabled_unauthorized(authenticated_client, mock_user, app):
    """Test toggling organize_enabled flag for a folder that doesn't belong to the user."""
    # Create a folder that belongs to a different user
    other_user = User(email='other@example.com', first_name='Other', last_name='User')
    other_user.set_password('password')
    
        db.session.add(other_user)
        
        # Create a folder for the other user
        other_folder = Folder(
            user_id=other_user.id,
            name='Other User Folder',
            rule_text='Test rule text'
        )
        db.session.add(other_folder)
        
        # Try to toggle the flag for the folder that doesn't belong to authenticated user
        response = authenticated_client.put(f'/api/folders/{other_folder.id}/toggle')
        
        # Should return 404 Not Found (folder not found due to authorization check)
        assert response.status_code == 404

user_from_db = User.query.filter_by(email='test@example.com').first()

def test_user_model(app,  mock_user):
    """Test User model creation and properties."""
    # Test user was created by fixture
    assert mock_user.email == 'test@example.com'
    
    # Test querying user
    user_from_db = User.query.filter_by(email='test@example.com').first()
    assert user_from_db is not None
    assert user_from_db.id == mock_user.id

folder_from_db = Folder.query.filter_by(name='Test Folder').first()

def test_folder_model(app, mock_folder, mock_user):
    """Test Folder model creation and properties."""
    # Test folder was created by fixture
    assert mock_folder.user_id == mock_user.id
    assert mock_folder.name == 'Test Folder'
    assert mock_folder.rule_text == 'Test rule'
    assert mock_folder.priority == 1
    
    # Test relationship
    assert len(mock_user.folders) == 1
    assert mock_user.folders[0].id == mock_folder.id
    
    # Test querying folder
    folder_from_db = Folder.query.filter_by(name='Test Folder').first()
    assert folder_from_db is not None
    assert folder_from_db.user_id == mock_user.id

def test_init_with_user(self, app, mock_user):
        """Test ProcessedEmailsService initialization with a user."""
        service = ProcessedEmailsService(mock_user)
        assert service.user == mock_user

def test_get_pending_emails_empty(self, app, mock_user):
        """Test get_pending_emails when no emails are pending."""
        
        # Mock a folder
            rule_text='Test rule'
        )
        
        # Test with no pending emails
        pending_uids = service.get_pending_emails('Test Folder')
        assert len(pending_uids) == 0

def test_get_pending_emails_with_data(self, app, mock_user):
        """Test get_pending_emails when there are pending emails."""
            service = ProcessedEmailsService(mock_user)
            
            # Mock a folder
            )
            
                ),
                )
            ]
            
            # Test getting pending emails
            pending_uids = service.get_pending_emails('Test Folder')
            assert len(pending_uids) == 2
            assert '123' in pending_uids
            assert '456' in pending_uids

def test_mark_email_processed_existing(self, app, mock_user):
        """Test mark_email_processed for an existing pending email."""
            
            )
            
            )
            
            # Mark email as processed
            success = service.mark_email_processed('Test Folder', '123')
            assert success is True
            
            ).first()
            assert updated_email.processed_at is not None

def test_mark_email_processed_new(self, app, mock_user):
        """Test mark_email_processed for a new email record."""
            
            )
            
            # Mark email as processed (create new record)
            success = service.mark_email_processed('Test Folder', '789')
            
            # Verify the record was created
            new_email = ProcessedEmail.query.filter_by(
                email_uid='789'
            ).first()
            assert new_email is not None
            assert new_email.is_processed is True
            assert new_email.processed_at is not None

processed_count = service.mark_emails_processed('Test Folder', ['123', '456', '789'])

def test_mark_emails_processed(self, app, mock_user):
        """Test mark_emails_processed for multiple emails."""
            
            )
            
                ),
                )
            ]
            
            # Mark multiple emails as processed
            processed_count = service.mark_emails_processed('Test Folder', ['123', '456', '789'])
            assert processed_count == 3
            
            # Verify all emails are marked as processed
            processed_uids = set()
            for email in ProcessedEmail.query.filter_by(
                folder_name='Test Folder'
            ).all():
                processed_uids.add(email.email_uid)
                assert email.is_processed is True
            
            assert '123' in processed_uids
            assert '456' in processed_uids
            assert '789' in processed_uids

new_emails_count = service.sync_folder_emails('Test Folder', email_uids)

def test_sync_folder_emails(self, app, mock_user):
        """Test sync_folder_emails for a folder."""
            
            )
            
            # Sync emails
            email_uids = ['123', '456', '789']
            new_emails_count = service.sync_folder_emails('Test Folder', email_uids)
            assert new_emails_count == 3
            
            # Verify records were created
            records = ProcessedEmail.query.filter_by(
            ).all()
            assert len(records) == 3
            
            # Verify folder counts
            updated_folder = Folder.query.filter_by(id=folder.id).first()
            assert updated_folder.total_count == 3
            assert updated_folder.pending_count == 3

def test_get_pending_count(self, app, mock_user):
        """Test get_pending_count for a folder."""
            
            )
            
            # Create some pending and processed emails
            emails = [
                    email_uid='1',
                ),
                    email_uid='2',
                ),
                    email_uid='3',
                    is_processed=True
                )
            ]
            db.session.bulk_save_objects(emails)
            
            # Test pending count
            pending_count = service.get_pending_count('Test Folder')
            assert pending_count == 2

deleted_count = service.cleanup_old_records('Test Folder', current_uids)

def test_cleanup_old_records(self, app, mock_user):
        """Test cleanup_old_records for a folder."""
            
            )
            
            # Create some email records
            emails = [
                ),
                ),
                )
            ]
            
            # Clean up records that don't exist in current UIDs
            current_uids = ['1', '2']
            deleted_count = service.cleanup_old_records('Test Folder', current_uids)
            assert deleted_count == 1
            
            # Verify only existing records remain
            remaining_records = ProcessedEmail.query.filter_by(
            ).all()
            assert len(remaining_records) == 2
            assert all(email.email_uid in current_uids for email in remaining_records)
            
            assert updated_folder.total_count == 2
            assert updated_folder.pending_count == 2

class TestProcessedEmailsService:
    def test_init_with_user(self, app, mock_user):
    
    def test_get_pending_emails_empty(self, app, mock_user):
        
        )
        
    def test_get_pending_emails_with_data(self, app, mock_user):
            
            )
            
                ),
                )
            ]
            
    def test_mark_email_processed_existing(self, app, mock_user):
            
            )
            
            )
            
            ).first()
    
    def test_mark_email_processed_new(self, app, mock_user):
            
            )
            
            ).first()
    
    def test_mark_emails_processed(self, app, mock_user):
            
            )
            
                ),
                )
            ]
            
            ).all():
            
    def test_sync_folder_emails(self, app, mock_user):
            
            )
            
            ).all()
            
    def test_get_pending_count(self, app, mock_user):
            
            )
            
            emails = [
                ),
                ),
                )
            ]
            
    def test_cleanup_old_records(self, app, mock_user):
            
            )
            
            emails = [
                ),
                ),
                )
            ]
            
            ).all()
            
def setup_method(self):
       """Set up test fixtures."""
       self.ai_service = AIService()
       # Set the attributes directly since they're not set in __init__ for tests
       self.ai_service.api_key = 'test-api-key'
       self.ai_service.model = 'test-model'
       self.ai_service.api_url = 'https://api.openai.com/v1'

def test_init(self):
        """Test AI service initialization."""
        assert self.ai_service.api_key == 'test-api-key'
        assert self.ai_service.model == 'test-model'
        assert self.ai_service.timeout == 30
        assert self.ai_service.max_retries == 3

def test_generate_multiple_rules_success(self):
        """Test successful multiple rules generation."""
        # This test should be updated to test the generate_multiple_rules method
        # For now, we'll skip this test since we're removing single rule functionality
        pass

def test_generate_multiple_rules_failure(self):
        """Test multiple rules generation failure."""
        pass

rule_text = "Move emails from 'boss@company.com' to this folder"

score = self.ai_service._assess_rule_quality(rule_text, folder_name, 'destination')

def test_assess_rule_quality(self):
        """Test rule quality assessment."""
        rule_text = "Move emails from 'boss@company.com' to this folder"
        folder_name = "Work"
        
        score = self.ai_service._assess_rule_quality(rule_text, folder_name, 'destination')
        
        assert isinstance(score, int)
        assert 0 <= score <= 100

def test_get_quality_grade(self):
        """Test quality grade determination."""
        assert self.ai_service._get_quality_grade(90) == 'excellent'
        assert self.ai_service._get_quality_grade(70) == 'good'
        assert self.ai_service._get_quality_grade(50) == 'fair'
        assert self.ai_service._get_quality_grade(30) == 'poor'

feedback = self.ai_service._generate_quality_feedback(rule_text, folder_name, score)

def test_generate_quality_feedback(self):
        """Test quality feedback generation."""
        score = 85
        
        feedback = self.ai_service._generate_quality_feedback(rule_text, folder_name, score)
        
        assert isinstance(feedback, str)
        assert len(feedback) > 0

def test_get_fallback_rule(self):
        """Test fallback rule generation."""
        rule = self.ai_service.get_fallback_rule('Work', 'destination')
        
        assert isinstance(rule, str)
        assert len(rule) > 0
        assert 'Work' in rule

key1 = AIService.generate_cache_key('Work', 'destination', 'multiple')

key2 = AIService.generate_cache_key('Work', 'destination', 'multiple')

key3 = AIService.generate_cache_key('Personal', 'destination', 'multiple')

def test_cache_key_generation(self):
        """Test cache key generation."""
        # Access the static method directly since it's not a bound method
        from app.ai_service import AIService
        key1 = AIService.generate_cache_key('Work', 'destination', 'multiple')
        key2 = AIService.generate_cache_key('Work', 'destination', 'multiple')
        key3 = AIService.generate_cache_key('Personal', 'destination', 'multiple')
        
        # Same inputs should produce same key
        assert key1 == key2
        # Different inputs should produce different keys
        assert key1 != key3

rules = self.ai_service._parse_multiple_rules_response(response_text)

def test_parse_multiple_rules_response(self):
        """Test parsing of multiple rules response."""
        response_text = '''
        {
            "rules": [
                {
                    "text": "Move emails from 'boss@company.com' to this folder",
                    "criteria": "Filters emails from specific sender"
                },
                {
                    "text": "Move emails with 'urgent' in subject to this folder",
                    "criteria": "Filters emails with urgent keywords"
                }
            ]
        }
        '''
        
        rules = self.ai_service._parse_multiple_rules_response(response_text)
        
        assert len(rules) == 2
        assert rules[0]['text'] == "Move emails from 'boss@company.com' to this folder"
        assert rules[0]['criteria'] == "Filters emails from specific sender"
        assert rules[1]['text'] == "Move emails with 'urgent' in subject to this folder"
        assert rules[1]['criteria'] == "Filters emails with urgent keywords"

def test_parse_multiple_rules_response_manual(self):
        """Test manual parsing of multiple rules response."""
        # Test with a more structured format that matches what the parser expects
        response_text = '''{
    "rules": [
        {
            "text": "Move emails from 'boss@company.com' to this folder",
            "criteria": "Filters emails from specific sender"
        },
        {
            "text": "Move emails with 'urgent' in subject to this folder",
            "criteria": "Filters emails with urgent keywords"
        }
    ]
}'''
        
        # Should parse JSON format correctly

def test_short_rule_penalty(self):
        """Test that short rules get penalized."""
        rule_text = "short"
        
        # Short rules should get low scores
        assert score < 50

rule_text = "This is a very long rule that exceeds the optimal length and should be penalized accordingly"

def test_long_rule_penalty(self):
        """Test that very long rules get penalized."""
        rule_text = "This is a very long rule that exceeds the optimal length and should be penalized accordingly"
        
        # Very long rules should get lower scores (should be <= 80)
        assert score <= 80

def test_specific_keyword_bonus(self):
        """Test that specific keywords get bonus points."""
        
        # Rules with specific keywords should get higher scores
        assert score > 50

def test_action_word_bonus(self):
        """Test that action words get bonus points."""
        
        # Rules with action words should get higher scores

rule_text = "Move emails related to 'Work' projects to this folder"

def test_folder_relevance_bonus(self):
        """Test that folder name relevance gets bonus points."""
        rule_text = "Move emails related to 'Work' projects to this folder"
        
        # Rules relevant to folder name should get higher scores

class TestAIService:
    """Test cases for the AI service functionality."""
    
    def setup_method(self):
        
    def test_init(self):
        
    def test_generate_multiple_rules_success(self):
        pass
        
    def test_generate_multiple_rules_failure(self):
        pass
        
    def test_assess_rule_quality(self):
        
    def test_get_quality_grade(self):
        
    def test_generate_quality_feedback(self):
        score = 85
        
    def test_get_fallback_rule(self):
        
    def test_cache_key_generation(self):
        
    def test_parse_multiple_rules_response(self):
        {
            "rules": [
                {
                },
                {
                }
            ]
        }
        '''
        
    def test_parse_multiple_rules_response_manual(self):
    "rules": [
        {
        },
        {
        }
    ]
}'''
        
    def test_short_rule_penalty(self):
        
    def test_long_rule_penalty(self):
        
    def test_specific_keyword_bonus(self):
        
    def test_action_word_bonus(self):
        
    def test_folder_relevance_bonus(self):
        
user = User(email='test@example.com', first_name='Test', last_name='User')

def test_init_with_user(self, app):
        user = User(email='test@example.com', first_name='Test', last_name='User')
        user.imap_config = {'server': 'test.com', 'port': 993}
        imap_service = IMAPService(user)
        assert imap_service.user == user
        assert imap_service.config == {'server': 'test.com', 'port': 993}

def test_init_without_config(self, app):
        assert imap_service.config == {}

def test_test_connection_success(self,  app):
        user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': True}
        
        # Mock the IMAP connection
        with patch('app.imap_service.imaplib.IMAP4_SSL') as mock_imap_ssl:
            mock_connection = mock_imap_ssl.return_value
            mock_connection.login.return_value = None
            mock_connection.select.return_value = ('OK', [b'1'])
            mock_connection.close.return_value = None
            mock_connection.logout.return_value = None
            
            success, message = imap_service.test_connection()
            
            assert message == "Connection successful"

def test_test_connection_failure(self, app):
        user.imap_config = {'server': 'test.com', 'port': 993, 'username': 'user', 'password': 'pass'}
        
        # Mock the IMAP connection to raise a generic exception
            mock_connection.login.side_effect = Exception("Connection failed")
            
            assert success is False
            assert "Connection error: Connection failed" in message

def test_test_connection_imap_error(self, app):
        # Mock the IMAP connection to raise an IMAP error
            mock_connection.login.side_effect = imaplib.IMAP4.error("IMAP login failed")
            
            user = User(email='test@example.com', first_name='Test', last_name='User')
            user.imap_config = {'server': 'test.com', 'port': 993, 'username': 'user', 'password': 'pass'}
            imap_service = IMAPService(user)
            
            assert "IMAP connection error: IMAP login failed" in message

def test_test_connection_no_config(self, app):
        
        success, message = imap_service.test_connection()
        
        assert success is False
        assert message == "No IMAP configuration found"

def test_get_folders_success(self, app):
        
            mock_connection.list.return_value = ('OK', [
                b'(\\HasNoChildren) "INBOX"',
                b'(\\HasNoChildren) "Sent"',
                b'(\\HasNoChildren) "Drafts"'
            ])
            
            folders = imap_service.get_folders()
            
            assert len(folders) == 3
            assert folders[0]['name'] == 'INBOX'
            assert folders[1]['name'] == 'Sent'
            assert folders[2]['name'] == 'Drafts'

def test_get_folders_empty(self, app):
        # Mock the IMAP connection to return an empty list of folders
            mock_connection.list.return_value = ('OK', [])
            
            assert len(folders) == 0

def test_get_folders_error(self, app):
        
        # Mock the IMAP connection to raise an exception
            mock_connection.list.side_effect = Exception("IMAP error")
            
def test_get_folders_no_config(self, app):
        
        folders = imap_service.get_folders()
        
        assert len(folders) == 0

fresh_user = User.query.filter_by(email='test@example.com').first()

def test_sync_folders_success(self, app):
            user.set_password('testpassword')
            user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1'}
        
                b'(\\HasNoChildren) "CustomFolder1"',
                b'(\\HasNoChildren) "CustomFolder2"'
            ])
            
            # Mock the get_folder_email_count method
            with patch.object(imap_service, 'get_folder_email_count') as mock_count:
                mock_count.return_value = 5
                
                # Mock the get_recent_emails method
                with patch.object(imap_service, 'get_recent_emails') as mock_recent:
                    mock_recent.return_value = [
                        {'subject': 'Test Email 1', 'date': '2023-01-01T12:00:00'},
                        {'subject': 'Test Email 2', 'date': '2023-01-02T12:00:00'}
                    ]
                    
                    # Get fresh user from database and create new IMAPService to fix DetachedInstanceError
                    fresh_user = User.query.filter_by(email='test@example.com').first()
                    fresh_user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1'}
                    fresh_imap_service = IMAPService(fresh_user)
                    
                    # Mock the get_folder_email_count method on the new service
                    with patch.object(fresh_imap_service, 'get_folder_email_count') as mock_count:
                        mock_count.return_value = 5
                        
                        # Mock the get_recent_emails method on the new service
                        with patch.object(fresh_imap_service, 'get_recent_emails') as mock_recent:
                            mock_recent.return_value = [
                                {'subject': 'Test Email 1', 'date': '2023-01-01T12:00:00'},
                                {'subject': 'Test Email 2', 'date': '2023-01-02T12:00:00'}
                            ]
                            
                            success, message = fresh_imap_service.sync_folders()
                            
                            assert success is True
                            assert "Successfully synced 2 folders" in message

def test_sync_folders_no_config(self, app):
        
        success, message = imap_service.sync_folders()
        
def test_sync_folders_no_folders(self, app):
            user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False}
            
            # Mock the get_folders method to return an empty list
            with patch.object(imap_service, 'get_folders', return_value=[]):
                success, message = imap_service.sync_folders()
                
                assert success is False
                assert message == "No folders found on IMAP server"

def test_sync_folders_exception(self, app):
             
            user.imap_config = {'server': 'localhost', 'port': 5143, 'username': 'user1@example.com', 'password': 'password1', 'use_ssl': False }
            
            # Mock the get_folders method to raise an exception
            with patch.object(imap_service, 'get_folders', side_effect=Exception("IMAP server error")):
                
                assert "Sync error: IMAP server error" in message

class TestIMAPService:
    def test_init_with_user(self, app):
    
    def test_init_without_config(self, app):
    
    def test_test_connection_success(self,  app):
        
    def test_test_connection_failure(self, app):
        
    def test_test_connection_imap_error(self, app):
            
    def test_test_connection_no_config(self, app):
        
    def test_get_folders_success(self, app):
        
            ])
            
    def test_get_folders_empty(self, app):
            
    def test_get_folders_error(self, app):
        
    def test_get_folders_no_config(self, app):
        
    def test_sync_folders_success(self, app):
        
            ])
            
                    ]
                    
                            ]
                            
    def test_sync_folders_no_config(self, app):
        
    def test_sync_folders_no_folders(self, app):
            
    def test_sync_folders_exception(self, app):
             
def get_engine():
    try:
        # this works with Flask-SQLAlchemy<3 and Alchemical
        return current_app.extensions['migrate'].db.get_engine()
    except (TypeError, AttributeError):
        # this works with Flask-SQLAlchemy>=3
        return current_app.extensions['migrate'].db.engine

def get_engine_url():
    try:
        return get_engine().url.render_as_string(hide_password=False).replace(
            '%', '%%')
    except AttributeError:
        return str(get_engine().url).replace('%', '%%')

def get_metadata():
    if hasattr(target_db, 'metadatas'):
        return target_db.metadatas[None]
    return target_db.metadata

def run_migrations_offline():

    """
        url=url, target_metadata=get_metadata(), literal_binds=True
    )

def process_revision_directives(context, revision, directives):
        if getattr(config.cmd_opts, 'autogenerate', False):
            script = directives[0]
            if script.upgrade_ops.is_empty():
                directives[:] = []
                logger.info('No changes in schema detected.')

def run_migrations_online():

    """

    # this callback is used to prevent an auto-migration from being generated
    # when there are no changes to the schema
    # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
    def process_revision_directives(context, revision, directives):

    conf_args = current_app.extensions['migrate'].configure_args
    if conf_args.get("process_revision_directives") is None:
        conf_args["process_revision_directives"] = process_revision_directives

    connectable = get_engine()

            connection=connection,
            target_metadata=get_metadata(),
            **conf_args
        )

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('folders', schema=None) as batch_op:
        batch_op.add_column(sa.Column('organize_enabled', sa.Boolean(), nullable=True))

def downgrade():
        batch_op.drop_column('organize_enabled')

    with op.batch_alter_table('users', schema=None) as batch_op:
        batch_op.alter_column('first_name',
               existing_type=sa.VARCHAR(length=255),
               nullable=False)
        batch_op.alter_column('last_name',
        batch_op.alter_column('password_hash',
               existing_type=postgresql.BYTEA(),
               type_=sa.String(length=2048),

               existing_type=sa.String(length=2048),
               type_=postgresql.BYTEA(),
               nullable=True)

    op.create_table('ai_rule_cache',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('user_id', sa.Integer(), nullable=False),
    sa.Column('folder_name', sa.String(length=255), nullable=False),
    sa.Column('folder_type', sa.String(length=20), nullable=False),
    sa.Column('rule_text', sa.Text(), nullable=False),
    sa.Column('rule_metadata', sa.JSON(), nullable=True),
    sa.Column('cache_key', sa.String(length=64), nullable=False),
    sa.Column('created_at', sa.DateTime(), nullable=True),
    sa.Column('expires_at', sa.DateTime(), nullable=False),
    sa.Column('is_active', sa.Boolean(), nullable=True),
    sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('cache_key')
    )

    op.drop_table('ai_rule_cache')

    op.create_table('users',
    sa.Column('email', sa.String(length=255), nullable=False),
    sa.Column('password_hash', sa.LargeBinary(), nullable=True),
    sa.Column('imap_config', sa.JSON(), nullable=True),
    sa.UniqueConstraint('email')
    )
    op.create_table('folders',
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.Column('rule_text', sa.Text(), nullable=True),
    sa.Column('priority', sa.Integer(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )

    op.drop_table('folders')
    op.drop_table('users')

    op.create_table('processed_emails',
    sa.Column('folder_id', sa.Integer(), nullable=False),
    sa.Column('email_uid', sa.String(length=255), nullable=False),
    sa.Column('is_processed', sa.Boolean(), nullable=True),
    sa.Column('first_seen_at', sa.DateTime(), nullable=True),
    sa.Column('processed_at', sa.DateTime(), nullable=True),
    sa.Column('updated_at', sa.DateTime(), nullable=True),
    sa.ForeignKeyConstraint(['folder_id'], ['folders.id'], ),
    )

    op.drop_table('processed_emails')

        batch_op.add_column(sa.Column('recent_emails', sa.JSON(), nullable=True))

        batch_op.drop_column('recent_emails')

        batch_op.add_column(sa.Column('first_name', sa.String(length=255), nullable=True))
        batch_op.add_column(sa.Column('last_name', sa.String(length=255), nullable=True))
        batch_op.add_column(sa.Column('created_at', sa.DateTime(), nullable=True))
        batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=True))

        batch_op.drop_column('updated_at')
        batch_op.drop_column('created_at')
        batch_op.drop_column('last_name')
        batch_op.drop_column('first_name')

        batch_op.add_column(sa.Column('total_count', sa.Integer(), nullable=True))
        batch_op.add_column(sa.Column('pending_count', sa.Integer(), nullable=True))

        batch_op.drop_column('pending_count')
        batch_op.drop_column('total_count')

    # Add new columns as nullable first
    op.add_column('folders', sa.Column('folder_type', sa.String(length=20), nullable=True))
    op.add_column('folders', sa.Column('emails_count', sa.Integer(), nullable=True))
    
    # Set default values for existing records
    # Set all existing folders to 'destination' type
    op.execute("UPDATE folders SET folder_type = 'destination' WHERE folder_type IS NULL")
    
    # Set 'inbox' folders to 'tidy' type
    op.execute("UPDATE folders SET folder_type = 'tidy' WHERE folder_type IS NULL AND name ILIKE '%inbox%'")
    
    # Now make folder_type NOT NULL
    op.alter_column('folders', 'folder_type', nullable=False)

    # Make column nullable again before dropping
    op.alter_column('folders', 'folder_type', nullable=True)
    
    # Drop the columns
    op.drop_column('folders', 'emails_count')
    op.drop_column('folders', 'folder_type')

def __init__(self):
        self.api_url = os.environ.get('AI_SERVICE_URL', 'https://api.openai.com/v1')
        self.api_key = os.environ.get('AI_SERVICE_API_KEY')
        self.model = os.environ.get('AI_MODEL', 'gpt-3.5-turbo')
        self.timeout = int(os.environ.get('AI_TIMEOUT', 30))
        self.max_retries = int(os.environ.get('AI_MAX_RETRIES', 3))
        self.cache_ttl = int(os.environ.get('AI_CACHE_TTL', 3600))  # 1 hour
        
        # Configure logging
        logging.basicConfig(level=logging.INFO)
        self.logger = logging.getLogger(__name__)

endpoint: str, payload: Dict, headers: Dict = None) -> Optional[Dict]:

def _make_request(self, endpoint: str, payload: Dict, headers: Dict = None) -> Optional[Dict]:
        """Make HTTP request to AI service with retry logic."""
        if not headers:
            headers = {}
            
        headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        })
        
        url = f"{self.api_url}/{endpoint}"
        
        for attempt in range(self.max_retries):
            try:
                response = requests.post(
                    url,
                    json=payload,
                    headers=headers,
                    timeout=self.timeout
                )
                response.raise_for_status()
                return response.json()
                
            except requests.exceptions.RequestException as e:
                self.logger.warning(f"AI service request failed (attempt {attempt + 1}/{self.max_retries}): {e}")
                if attempt == self.max_retries - 1:
                    self.logger.error(f"AI service request failed after {self.max_retries} attempts")
                    return None
                time.sleep(2 ** attempt)  # Exponential backoff
                self.logger.warning(f"Unexpected error in AI service request (attempt {attempt + 1}/{self.max_retries}): {e}")
                    self.logger.error(f"AI service request failed after {self.max_retries} attempts due to unexpected error")
                
folder_name: str, folder_type: str = 'destination', rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:

folder_type: str = 'destination', rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:

rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:

prompt = self._build_multiple_rules_prompt(folder_name, folder_type, rule_text, count)

response_text = result['choices'][0]['message']['content'].strip()

quality_score = self._assess_rule_quality(rule['text'], folder_name, folder_type)

def generate_multiple_rules(self, folder_name: str, folder_type: str = 'destination', rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:
        """Generate multiple email organization rule options using AI."""
        prompt = self._build_multiple_rules_prompt(folder_name, folder_type, rule_text, count)
        print("PROMPT", prompt)
        
        payload = {
            'model': self.model,
            'messages': [
                {'role': 'system', 'content': 'You are an expert email organizer assistant.'},
                {'role': 'user', 'content': prompt}
            ],
            'max_tokens': 400,
            'temperature': 0.8
        }
        
        result = self._make_request('chat/completions', payload)
        
        if not result or 'choices' not in result or not result['choices']:
            return None, {'error': 'No response from AI service'}
            
        response_text = result['choices'][0]['message']['content'].strip()
        print(f"RESPONSE WAS '{response_text}'")
        rules = self._parse_multiple_rules_response(response_text)
        
        if not rules:
            return None, {'error': 'Failed to parse AI response'}
            
        # Assess quality for each rule
        scored_rules = []
        for rule in rules:
            quality_score = self._assess_rule_quality(rule['text'], folder_name, folder_type)
            scored_rules.append({
                'text': rule['text'],
                'quality_score': quality_score,
                'key_criteria': rule.get('criteria', ''),
                'model_used': self.model,
                'generated_at': datetime.utcnow().isoformat()
            })
            
        return scored_rules, {
            'total_generated': len(scored_rules),
            'model_used': self.model,
            'generated_at': datetime.utcnow().isoformat()
        }

rule_text: str, folder_name: str, folder_type: str = 'destination') -> Dict:

score = self._assess_rule_quality(rule_text, folder_name, folder_type)

def assess_rule_quality(self, rule_text: str, folder_name: str, folder_type: str = 'destination') -> Dict:
        """Assess the quality of an email organization rule."""
        score = self._assess_rule_quality(rule_text, folder_name, folder_type)
        
        return {
            'score': score,
            'grade': self._get_quality_grade(score),
            'feedback': self._generate_quality_feedback(rule_text, folder_name, score),
            'assessed_at': datetime.utcnow().isoformat()
        }

def _build_single_rule_prompt(self, folder_name: str, folder_type: str, rule_text: str) -> str:
        """Build prompt for single rule generation."""
        return f"""
Generate a single, effective email organization rule for a folder named "{folder_name}".
This folder is of type "{folder_type}".
The current rule text is "{rule_text}". You can choose to enhance this.

Requirements:
1. The rule should be specific and actionable
2. Use natural language that can be easily understood
3. Focus on common email patterns that would benefit from organization
4. Keep it concise (under 150 characters)
5. Make it relevant to the folder name and purpose
6. Rules should follow the structure: Bulleted list (separated by new line) of * (content) belongs in this folder. * (content) DOES NOT belong in this folder

Return only the rule text, nothing else.
"""

folder_name: str, folder_type: str, rule_text: str, count: int) -> str:

def _build_multiple_rules_prompt(self, folder_name: str, folder_type: str, rule_text: str, count: int) -> str:
        """Build prompt for multiple rule generation."""
Generate {count} different email organization rule options for a folder named "{folder_name}".
The current rule text is "{rule_text}". If there is content in this, your options must respect the existing content.

1. Each rule should be specific and actionable
3. Focus on different aspects of email organization for this folder
4. Keep each rule concise (under 150 characters)
5. Make rules relevant to the folder name and purpose
6. Provide variety in rule approaches
7. A single rule option should should follow the structure: Bulleted list (separated by new line) of * (content) belongs in this folder. * (content) DOES NOT belong in this folder

Return the rules in JSON format:
{{
    "rules": [
        {{
            "text": "rule text here, as a bulleted list separated by newlines",
            "criteria": "brief explanation of what this rule targets"
        }},
        ...
    ]
}}
7. DO NOT use markdown at all. Respond with JSON.
8. Rules should follow the structure: Bulleted list (separated by newlines ,\\n) of * (content) belongs in this folder. * (content) DOES NOT belong in this folder

"""

def _parse_multiple_rules_response(self, response_text: str) -> List[Dict]:
        """Parse multiple rules response from AI."""
        try:
            # Try to parse as JSON first
            data = json.loads(response_text)
            print(f"DATAA WAS {data}")
            if 'rules' in data and isinstance(data['rules'], list):
                return data['rules']
                
            # If JSON parsing fails, try to extract rules manually
            rules = []
            lines = response_text.split('\n')
            current_rule = {}
            
            for line in lines:
                line = line.strip()
                if line.startswith('"text":') or line.startswith('"rule":'):
                    if current_rule:
                        rules.append(current_rule)
                    current_rule = {'text': line.split(':', 1)[1].strip().strip('"')}
                elif line.startswith('"criteria":') and current_rule:
                    current_rule['criteria'] = line.split(':', 1)[1].strip().strip('"')
                    
            if current_rule:
                rules.append(current_rule)
                
            return rules[:5]  # Return max 5 rules
            
        except json.JSONDecodeError:
            self.logger.warning("Failed to parse AI response as JSON, attempting manual parsing")
            return []

specific_keywords = ['from', 'subject', 'contains', 'sender', 'domain', 'email']

has_specific_keyword = any(keyword in rule_text.lower() for keyword in specific_keywords)

action_words = ['move', 'filter', 'organize', 'sort', 'categorize', 'send', 'redirect']

has_action_word = any(word in rule_text.lower() for word in action_words)

folder_relevance = sum(1 for word in folder_words if word in rule_text.lower())

def _assess_rule_quality(self, rule_text: str, folder_name: str, folder_type: str) -> int:
        """Assess rule quality and return score 0-100."""
        if not rule_text or len(rule_text.strip()) < 10:
            return 0
            
        score = 50  # Base score
        
        # Length check (optimal: 20-100 characters)
        rule_length = len(rule_text.strip())
        if 20 <= rule_length <= 100:
            score += 20
        elif 10 <= rule_length < 20 or 100 < rule_length <= 150:
            score += 10
            
        # Specificity check
        specific_keywords = ['from', 'subject', 'contains', 'sender', 'domain', 'email']
        has_specific_keyword = any(keyword in rule_text.lower() for keyword in specific_keywords)
        if has_specific_keyword:
            score += 20
            
        # Action-oriented check
        action_words = ['move', 'filter', 'organize', 'sort', 'categorize', 'send', 'redirect']
        has_action_word = any(word in rule_text.lower() for word in action_words)
        if has_action_word:
            score += 15
            
        # Relevance to folder name
        folder_words = folder_name.lower().split()
        folder_relevance = sum(1 for word in folder_words if word in rule_text.lower())
        if folder_relevance > 0:
            score += 15
            
        # Grammar and structure check
        if '.' not in rule_text and '?' not in rule_text and '!' not in rule_text:
            score += 10  # Simple, clean structure
            
        # Check for common rule patterns
        common_patterns = [
            r'from:.*@.*\..*',
            r'subject:.*',
            r'contains:.*',
            r'if.*then.*'
        ]
        
        import re
        for pattern in common_patterns:
            if re.search(pattern, rule_text, re.IGNORECASE):
                score += 10
                break
                
        return min(score, 100)  # Cap at 100

def _get_quality_grade(self, score: int) -> str:
        """Get quality grade based on score."""
        if score >= 80:
            return 'excellent'
        elif score >= 60:
            return 'good'
        elif score >= 40:
            return 'fair'
        else:
            return 'poor'

def _generate_quality_feedback(self, rule_text: str, folder_name: str, score: int) -> str:
        """Generate quality feedback based on rule assessment."""
        feedback = []
        
            feedback.append("Excellent rule! It's specific, actionable, and well-structured.")
            feedback.append("Good rule with room for improvement.")
            feedback.append("Fair rule. Consider making it more specific.")
        else:
            feedback.append("Poor rule. Needs significant improvement.")
            
        # Add specific feedback
        if len(rule_text.strip()) < 20:
            feedback.append("Rule is too short. Add more specific criteria.")
        elif len(rule_text.strip()) > 100:
            feedback.append("Rule is too long. Be more concise.")
            
        if not any(word in rule_text.lower() for word in ['from', 'subject', 'contains']):
            feedback.append("Consider adding specific criteria like 'from:' or 'subject:'.")
            
        if not any(word in rule_text.lower() for word in ['move', 'filter', 'organize']):
            feedback.append("Make sure the rule includes an action word.")
            
        return " ".join(feedback)

folder_name: str, folder_type: str, rule_type: str, raw_text: str) -> str:

key_string = f"{folder_name}:{folder_type}:{rule_type}:{raw_text}"

@staticmethod
    def generate_cache_key(folder_name: str, folder_type: str, rule_type: str, raw_text: str) -> str:
        """Generate a cache key for AI rule requests."""
        key_string = f"{folder_name}:{folder_type}:{rule_type}:{raw_text}"
        return hashlib.md5(key_string.encode()).hexdigest()

rules = fallback_rules.get(folder_type, fallback_rules['destination'])

def get_fallback_rule(self, folder_name: str, folder_type: str = 'destination') -> str:
        """Generate a fallback rule when AI service is unavailable."""
        fallback_rules = {
            'destination': [
                f"Move emails containing '{folder_name}' in the subject to this folder",
                f"Filter emails from senders with '{folder_name}' in their domain",
                f"Organize emails with '{folder_name}' keywords in the body"
            ],
            'tidy': [
                f"Move emails older than 30 days to this folder",
                f"Archive processed emails from '{folder_name}'",
                f"Sort completed emails by date"
            ],
            'ignore': [
                f"Ignore emails containing '{folder_name}'",
                f"Exclude emails from '{folder_name}' senders",
                f"Skip emails with '{folder_name}' in subject"
            ]
        }
        
        rules = fallback_rules.get(folder_type, fallback_rules['destination'])
        return rules[0] if rules else f"Move emails related to '{folder_name}' to this folder"

api_url = os.environ.get('AI_SERVICE_URL', 'https://api.openai.com/v1')

class AIService:
    """AI service layer for email rule generation and quality assessment."""
    
    def __init__(self):
        
    def _make_request(self, endpoint: str, payload: Dict, headers: Dict = None) -> Optional[Dict]:
            
        })
        
            try:
                    url,
                )
                
    def generate_multiple_rules(self, folder_name: str, folder_type: str = 'destination', rule_text:str = '', count: int = 3) -> Tuple[Optional[List[Dict]], Optional[Dict]]:
        
        payload = {
            ],
        }
        
            })
            
        }
    
    def assess_rule_quality(self, rule_text: str, folder_name: str, folder_type: str = 'destination') -> Dict:
        
        return {
        }
    
    def _build_single_rule_prompt(self, folder_name: str, folder_type: str, rule_text: str) -> str:

"""
    
    def _build_multiple_rules_prompt(self, folder_name: str, folder_type: str, rule_text: str, count: int) -> str:

{{
    "rules": [
        {{
        }},
        ...
    ]
}}

"""
    
    def _parse_multiple_rules_response(self, response_text: str) -> List[Dict]:
        try:
                
            rules = []
            
            return []
    
    def _assess_rule_quality(self, rule_text: str, folder_name: str, folder_type: str) -> int:
            return 0
            
            score += 20
            score += 10
            
            score += 20
            
            score += 15
            
            score += 15
            
        ]
        
        import re
                score += 10
                break
                
    def _get_quality_grade(self, score: int) -> str:
        else:
    
    def _generate_quality_feedback(self, rule_text: str, folder_name: str, score: int) -> str:
        
        else:
            
    @staticmethod
    
    def get_fallback_rule(self, folder_name: str, folder_type: str = 'destination') -> str:
            ],
            'tidy': [
            ],
            ]
        }
        
def get_pending_emails(self, folder_name: str) -> List[str]:
        """Get list of email UIDs that are pending processing in a folder."""
        try:
            pending_emails = ProcessedEmail.query.filter_by(
                user_id=self.user.id,
                folder_name=folder_name,
            ).all()
            
            return [email.email_uid for email in pending_emails]
        except Exception as e:
            print(f"Error getting pending emails: {str(e)}")
            return []

def mark_email_processed(self, folder_name: str, email_uid: str) -> bool:
        """Mark an email as processed."""
        try:
            processed_email = ProcessedEmail.query.filter_by(
                email_uid=email_uid
            ).first()
            
            if processed_email:
                processed_email.is_processed = True
                processed_email.processed_at = datetime.utcnow()
                processed_email.updated_at = datetime.utcnow()
                return True
            else:
                # Get the folder object to set the folder_id
                folder = Folder.query.filter_by(
                    user_id=self.user.id,
                    name=folder_name
                ).first()
                
                if folder:
                    # Create a new record if it doesn't exist
                    new_record = ProcessedEmail(
                        user_id=self.user.id,
                        folder_id=folder.id,
                        folder_name=folder_name,
                        email_uid=email_uid,
                        is_processed=True,
                        processed_at=datetime.utcnow()
                    )
                    db.session.add(new_record)
                    db.session.commit()
                    return True
                else:
                    return False
                
            print(f"Error marking email as processed: {str(e)}")
            db.session.rollback()
            return False

existing_uids = {email.email_uid for email in ProcessedEmail.query.filter_by(

def mark_emails_processed(self, folder_name: str, email_uids: List[str]) -> int:
        """Mark multiple emails as processed in bulk."""
        try:
            updated_count = 0
            
            # Update existing records
            ProcessedEmail.query.filter_by(
            ).filter(ProcessedEmail.email_uid.in_(email_uids)).update(
                {
                    'is_processed': True,
                    'processed_at': datetime.utcnow(),
                    'updated_at': datetime.utcnow()
                },
                synchronize_session=False
            )
            
            # Check for any email UIDs that don't have records yet
            existing_uids = {email.email_uid for email in ProcessedEmail.query.filter_by(
                folder_name=folder_name
            ).all()}
            
            missing_uids = set(email_uids) - existing_uids
            if missing_uids:
                ).first()
                
                if folder:
                    # Create new records for missing UIDs
                    new_records = [
                        ProcessedEmail(
                            user_id=self.user.id,
                            folder_id=folder.id,
                            folder_name=folder_name,
                            email_uid=uid,
                            is_processed=True,
                            processed_at=datetime.utcnow()
                        )
                        for uid in missing_uids
                    ]
                    db.session.bulk_save_objects(new_records)
            
            updated_count = len(email_uids)
            return updated_count
            
            print(f"Error marking emails as processed: {str(e)}")
            return 0

def sync_folder_emails(self, folder_name: str, email_uids: List[str]) -> int:
        """Sync email UIDs for a folder, adding new ones as pending."""
        try:
            # Get existing UIDs for this folder
            ).all()}
            
            # Find new UIDs that don't exist yet
            new_uids = set(email_uids) - existing_uids
            
            # Create new records for new UIDs
            if new_uids:
                ).first()
                
                if folder:
                            is_processed=False
                        )
                        for uid in new_uids
                    ]
            
            # Get the folder to update counts
            folder = Folder.query.filter_by(
                name=folder_name
            ).first()
            
            if folder:
                # Update pending count
                pending_count = ProcessedEmail.query.filter_by(
                    folder_name=folder_name,
                ).count()
                folder.pending_count = pending_count
                folder.total_count = len(email_uids)
            
            return len(new_uids)
            
            print(f"Error syncing folder emails: {str(e)}")
            return 0

def get_pending_count(self, folder_name: str) -> int:
        """Get count of pending emails for a folder."""
        try:
            count = ProcessedEmail.query.filter_by(
            ).count()
            return count
            print(f"Error getting pending count: {str(e)}")
            return 0

def cleanup_old_records(self, folder_name: str, current_uids: List[str]) -> int:
        """Remove records for emails that no longer exist in the folder."""
        try:
            # Find records that no longer exist in the current UIDs
            records_to_delete = ProcessedEmail.query.filter(
                and_(
                    ProcessedEmail.user_id == self.user.id,
                    ProcessedEmail.folder_name == folder_name,
                    ~ProcessedEmail.email_uid.in_(current_uids)
                )
            ).all()
            
            # Delete the records
            for record in records_to_delete:
                db.session.delete(record)
            
            deleted_count = len(records_to_delete)
            
            # Update folder counts after cleanup
            ).first()
            
            if folder:
                ).count()
                folder.total_count = len(current_uids)
            
            return deleted_count
            
            print(f"Error cleaning up old records: {str(e)}")
            return 0

class ProcessedEmailsService:
    def __init__(self, user: User):
        self.user = user
    
    def get_pending_emails(self, folder_name: str) -> List[str]:
        try:
            ).all()
            
            return []
    
    def mark_email_processed(self, folder_name: str, email_uid: str) -> bool:
        try:
            ).first()
            
            else:
                ).first()
                
                if folder:
                    )
                else:
                
    def mark_emails_processed(self, folder_name: str, email_uids: List[str]) -> int:
        try:
            
                {
                },
            )
            
            ).all()}
            
                ).first()
                
                if folder:
                        )
                    ]
            
            return 0
    
    def sync_folder_emails(self, folder_name: str, email_uids: List[str]) -> int:
        try:
            ).all()}
            
                ).first()
                
                if folder:
                        )
                    ]
            
            ).first()
            
            if folder:
                ).count()
            
            return 0
    
    def get_pending_count(self, folder_name: str) -> int:
        try:
            ).count()
            return 0
    
    def cleanup_old_records(self, folder_name: str, current_uids: List[str]) -> int:
        try:
                and_(
                )
            ).all()
            
            ).first()
            
            if folder:
                ).count()
            
            return 0

def __init__(self, app=None, interval_minutes=5):
        self.app = app
        self.interval = interval_minutes * 60  # Convert to seconds
        self.thread = None
        self.running = False
        
        if app:
            self.init_app(app)

def init_app(self, app):
        """Initialize the scheduler with a Flask application."""
        
        # Store scheduler in app extensions
        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['scheduler'] = self

def start(self):
        """Start the background scheduler thread."""
        if self.running:
            self.logger.warning("Scheduler is already running")
            return
            
        self.running = True
        self.thread = Thread(target=self._run, daemon=True)
        self.thread.start()
        self.logger.info(f"Scheduler started with {self.interval//60} minute interval")

def stop(self):
        """Stop the background scheduler."""
        if self.thread:
            self.logger.info("Scheduler stopped")
            self.thread.join()

def _run(self):
        """Main loop for the scheduler."""
        # Wait a moment before first run to allow app to start
        time.sleep(5)
        
        while self.running:
            try:
                self.logger.info("Starting scheduled email processing")
                self.process_all_users()
                
                # Calculate next run time
                next_run = datetime.now() + timedelta(seconds=self.interval)
                self.logger.info(f"Next run at {next_run.strftime('%Y-%m-%d %H:%M:%S')}")
                
                # Sleep for the interval, checking every 10 seconds if we should stop
                slept = 0
                while self.running and slept < self.interval:
                    sleep_time = min(10, self.interval - slept)
                    time.sleep(sleep_time)
                    slept += sleep_time
                    
                self.logger.error(f"Error in scheduler: {str(e)}")
                # Continue running even if there's an error
                continue

def process_all_users(self):
        """Process emails for all users."""
        with self.app.app_context():
            try:
                # Get all users
                users = User.query.all()
                
                if not users:
                    self.logger.info("No users found for processing")
                    return
                
                self.logger.info(f"Processing emails for {len(users)} users")
                
                # Process each user's emails
                for user in users:
                    try:
                        processor = EmailProcessor(user)
                        result = processor.process_user_emails()
                        
                        self.logger.info(f"Completed processing for user {user.email}: "
                                      f"{result['success_count']} success, "
                                      f"{result['error_count']} errors, "
                                      f"{len(result['processed_folders'])} folders processed")
                        
                    except Exception as e:
                        self.logger.error(f"Error processing emails for user {user.email}: {str(e)}")
                        continue
                        
                self.logger.error(f"Error in process_all_users: {str(e)}")

class Scheduler:
    """
    Background scheduler for email processing tasks.
    Runs as a separate thread to process emails at regular intervals.
    """
    
    def __init__(self, app=None, interval_minutes=5):
        
        if app:
    
    def init_app(self, app):
        
    def start(self):
            return
            
    def stop(self):
    
    def _run(self):
        
            try:
                
                slept = 0
                    
                continue
    
    def process_all_users(self):
            try:
                
                    return
                
                    try:
                        
                        continue
                        
@app.cli.command("start-scheduler")
@click.option('--interval', default=5, help='Interval in minutes between processing runs (default: 5)')
def start_scheduler(interval):
    """Start the background email processing scheduler."""
    if not hasattr(app, 'scheduler'):
        print("Scheduler not available. Make sure app/scheduler.py exists and is properly imported.")
    
    print(f"Starting email processing scheduler with {interval} minute interval...")
    print("Press Ctrl+C to stop")
    
    try:
        # Start the scheduler
        app.scheduler.start()
        
        # Keep the main thread alive
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            print("\nShutting down scheduler...")
            app.scheduler.stop()
            
        print(f"Error starting scheduler: {e}")

id = db.Column(db.Integer, primary_key=True, autoincrement=True)

imap_config = db.Column(db.JSON) # Using db.JSON instead of db.JSONB for compatibility

updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

def set_password(self, password):
        """Hash and set the password."""
        self.password_hash = generate_password_hash(password)

def check_password(self, password):
        """Check if the provided password matches the stored hash."""
        return check_password_hash(self.password_hash, password)

def __repr__(self):
        return f'<User {self.first_name} {self.last_name} ({self.email})>'

class User(Base, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    first_name = db.Column(db.String(255), nullable=False)
    last_name = db.Column(db.String(255), nullable=False)
    email = db.Column(db.String(255), unique=True, nullable=False)
    password_hash = db.Column(db.String(2048), nullable=False)
    imap_config = db.Column(db.JSON) # Using db.JSON instead of db.JSONB for compatibility
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    def set_password(self, password):
    
    def check_password(self, password):
    
    def __repr__(self):

user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

folder_type = db.Column(db.String(20), default='destination', nullable=False)

recent_emails = db.Column(db.JSON, default=list)  # Store recent email subjects with dates

user = db.relationship('User', backref=db.backref('folders', lazy=True))

class Folder(Base):
    __tablename__ = 'folders'
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    name = db.Column(db.String(255), nullable=False)
    rule_text = db.Column(db.Text)
    priority = db.Column(db.Integer)
    organize_enabled = db.Column(db.Boolean, default=True)
    folder_type = db.Column(db.String(20), default='destination', nullable=False)
    total_count = db.Column(db.Integer, default=0)
    pending_count = db.Column(db.Integer, default=0)
    emails_count = db.Column(db.Integer, default=0)
    recent_emails = db.Column(db.JSON, default=list)  # Store recent email subjects with dates
    
    user = db.relationship('User', backref=db.backref('folders', lazy=True))

folder_id = db.Column(db.Integer, db.ForeignKey('folders.id'), nullable=False)

user = db.relationship('User', backref=db.backref('processed_emails', lazy=True))

folder = db.relationship('Folder', backref=db.backref('processed_emails', lazy=True))

        return f'<ProcessedEmail {self.email_uid} for folder {self.folder_name}>'

class ProcessedEmail(Base):
    __tablename__ = 'processed_emails'
    folder_id = db.Column(db.Integer, db.ForeignKey('folders.id'), nullable=False)
    email_uid = db.Column(db.String(255), nullable=False)
    folder_name = db.Column(db.String(255), nullable=False)
    is_processed = db.Column(db.Boolean, default=False)
    first_seen_at = db.Column(db.DateTime, default=datetime.utcnow)
    processed_at = db.Column(db.DateTime, nullable=True)
    
    user = db.relationship('User', backref=db.backref('processed_emails', lazy=True))
    folder = db.relationship('Folder', backref=db.backref('processed_emails', lazy=True))
    
rule_metadata = db.Column(db.JSON)  # Quality score, model info, etc.

cache_key = db.Column(db.String(64), unique=True, nullable=False)  # MD5 hash of inputs

user = db.relationship('User', backref=db.backref('ai_rule_cache', lazy=True))

        return f'<AIRuleCache {self.folder_name} for user {self.user_id}>'

folder_name: str, folder_type: str, rule_type: str = 'single', rule_text: str = '') -> str:

folder_type: str, rule_type: str = 'single', rule_text: str = '') -> str:

input_string = f"{folder_name}:{folder_type}:{rule_type}:{rule_text}"

    def generate_cache_key(folder_name: str, folder_type: str, rule_type: str = 'single', rule_text: str = '') -> str:
        """Generate a unique cache key based on inputs."""
        input_string = f"{folder_name}:{folder_type}:{rule_type}:{rule_text}"
        return hashlib.md5(input_string.encode()).hexdigest()

def is_expired(self) -> bool:
        """Check if cache entry is expired."""
        return datetime.utcnow() > self.expires_at

class AIRuleCache(Base):
    """Cache for AI-generated rules to improve performance and reduce API calls."""
    __tablename__ = 'ai_rule_cache'
    
    folder_type = db.Column(db.String(20), nullable=False)
    rule_text = db.Column(db.Text, nullable=False)
    rule_metadata = db.Column(db.JSON)  # Quality score, model info, etc.
    cache_key = db.Column(db.String(64), unique=True, nullable=False)  # MD5 hash of inputs
    expires_at = db.Column(db.DateTime, nullable=False)
    is_active = db.Column(db.Boolean, default=True)
    
    user = db.relationship('User', backref=db.backref('ai_rule_cache', lazy=True))
    
    def is_expired(self) -> bool:

app = Flask(__name__, static_folder='static', static_url_path='/static')

@login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

def create_app(config_name='default'):
    app = Flask(__name__, static_folder='static', static_url_path='/static')
    app.config.from_object(config[config_name])
    
    # Initialize extensions
    db.init_app(app)
    migrate = Migrate(app, db)
    login_manager.init_app(app)
    
    # Initialize and register scheduler if available
    if Scheduler:
        scheduler = Scheduler(app)
        app.scheduler = scheduler
    
    # Register blueprints
    from app.routes import main
    from app.auth import auth
    app.register_blueprint(main)
    app.register_blueprint(auth, url_prefix='/auth')
    
    # User loader for Flask-Login
    @login_manager.user_loader
    
    return app

def __init__(self, user: User):
        self.config = user.imap_config or {}
        self.connection = None
        self.processed_emails_service = ProcessedEmailsService(user)

def _connect(self):
        """Create an IMAP connection based on configuration."""
        server = self.config.get('server', 'imap.gmail.com')
        port = self.config.get('port', 993)
        use_ssl = self.config.get('use_ssl', True)
        
        if use_ssl:
            # Create SSL context
            context = ssl.create_default_context()
            # Connect using SSL
            self.connection = imaplib.IMAP4_SSL(server, port)
        else:
            # Connect without SSL
            self.connection = imaplib.IMAP4(server, port)

def test_connection(self) -> Tuple[bool, str]:
        """Test IMAP connection with current configuration."""
        try:
            if not self.config:
                return False, "No IMAP configuration found"
            
            # Connect to IMAP server
            self._connect()
            
            # Login
            self.connection.login(
                self.config.get('username', ''),
                self.config.get('password', '')
            )
            
            # Select inbox to verify connection
            resp_code, content = self.connection.select('INBOX')
            print(resp_code, content)
            
            # Close the folder, not the connection
            self.connection.close()
            
            # Logout
            self.connection.logout()
            self.connection = None
            
            return True, "Connection successful"
            
        except imaplib.IMAP4.error as e:
            print(e)
            import traceback
            traceback.print_exc()
            return False, f"IMAP connection error: {str(e)}"
            print(e)
            return False, f"Connection error: {str(e)}"
        finally:
            if self.connection:
                try:
                    self.connection.logout()
                except:
                    pass

def get_folders(self) -> List[Dict[str, str]]:
        """Get list of folders from IMAP server."""
        try:
                return []
            
            # Login
            )
            
            # List folders
            status, folder_data = self.connection.list()
            
            if status != 'OK':
                return []

            print(folder_data)
            
            folders = []
            for folder_item in folder_data:
                if isinstance(folder_item, bytes):
                    folder_item = folder_item.decode('utf-8')
                
                # Parse folder name (handle different IMAP server formats)
                parts = folder_item.split('"')
                if len(parts) >= 3:
                    folder_name = parts[-1] if parts[-1] else parts[-2]
                else:
                    folder_name = folder_item.split()[-1]
                
                # Handle nested folders (convert to slash notation)
                if folder_name.startswith('"') and folder_name.endswith('"'):
                    folder_name = folder_name[1:-1]
                
                folders.append({
                    'name': folder_name,
                    'full_path': folder_name
                })
            
            return folders
            
            logging.error(f"Error fetching IMAP folders: {str(e)}")
            return []
        finally:
                try:
                except:
                    pass

def get_folder_email_count(self, folder_name: str) -> int:
        """Get the count of emails in a specific folder."""
        try:
            
            # Login
            )
            
            # Select the folder
            resp_code, content = self.connection.select(folder_name)
            if resp_code != 'OK':
                return 0
            
            # Get email count
            resp_code, content = self.connection.search(None, 'ALL')
                return 0
            
            # Count the emails
            email_ids = content[0].split()
            count = len(email_ids)
            
            # Close folder and logout
            
            logging.error(f"Error getting email count for folder {folder_name}: {str(e)}")
                try:
                except:
                    pass
            return 0

recent_email_ids = email_ids[:limit] if len(email_ids) >= limit else email_ids

email_id in reversed(recent_email_ids):  # Process from newest to oldest

email_date = parsedate_to_datetime(date_str) if date_str else None

def get_recent_emails(self, folder_name: str, limit: int = 3) -> List[Dict[str, any]]:
        """Get the most recent email subjects and dates from a specific folder."""
        try:
            
            # Login
            )
            
                return []
            
            # Get email IDs (most recent first)
                return []
            
            # Get the most recent emails (limit to 3)
            recent_email_ids = email_ids[:limit] if len(email_ids) >= limit else email_ids
            
            recent_emails = []
            for email_id in reversed(recent_email_ids):  # Process from newest to oldest
                # Fetch the email headers
                resp_code, content = self.connection.fetch(email_id, '(RFC822.HEADER)')
                if resp_code != 'OK':
                    continue
                
                # Parse the email headers
                raw_email = content[0][1]
                import email
                msg = email.message_from_bytes(raw_email)
                
                # Extract subject and date
                subject = msg.get('Subject', 'No Subject')
                date_str = msg.get('Date', '')
                
                # Decode the subject if needed
                try:
                    decoded_parts = decode_header(subject)
                    subject = ''.join([str(part, encoding or 'utf-8') if isinstance(part, bytes) else part for part, encoding in decoded_parts])
                except Exception:
                    pass  # If decoding fails, use the original subject
                
                # Parse date if available
                try:
                    email_date = parsedate_to_datetime(date_str) if date_str else None
                    email_date = None
                
                recent_emails.append({
                    'subject': subject,
                    'date': email_date.isoformat() if email_date else None
                })
            
            return recent_emails
            
            logging.error(f"Error getting recent emails for folder {folder_name}: {str(e)}")
                try:
                except:
                    pass
            return []

def get_folder_email_uids(self, folder_name: str) -> List[str]:
        """Get the list of email UIDs in a specific folder."""
        try:
            
            # Login
            )
            
                return []
            
            # Get email UIDs
                return []
            
            # Extract UIDs
            email_uids = content[0].split()
            uid_list = [uid.decode('utf-8') for uid in email_uids]
            
            return uid_list
            
            logging.error(f"Error getting email UIDs for folder {folder_name}: {str(e)}")
                try:
                except:
                    pass
            return []

        try:
            return self.processed_emails_service.get_pending_emails(folder_name)
            logging.error(f"Error getting pending emails for folder {folder_name}: {str(e)}")
            return []

def get_email_headers(self, folder_name: str, email_uid: str) -> Dict[str, str]:
        """Get email headers for a specific email UID."""
        try:
            
            # Login
            )
            
                return {}
            
            # Fetch email headers
            resp_code, content = self.connection.fetch(email_uid, '(RFC822.HEADER)')
                return {}
            
            # Parse the email headers
            raw_email = content[0][1]
            import email
            msg = email.message_from_bytes(raw_email)
            
            # Extract headers
            headers = {
                'subject': msg.get('Subject', 'No Subject'),
                'date': msg.get('Date', ''),
                'from': msg.get('From', ''),
                'to': msg.get('To', ''),
                'message_id': msg.get('Message-ID', '')
            }
            
            return headers
            
            logging.error(f"Error getting email headers for UID {email_uid} in folder {folder_name}: {str(e)}")
                try:
                except:
                    pass
            return {}

new_uids_count = self.processed_emails_service.sync_folder_emails(

pending_count = self.processed_emails_service.get_pending_count(folder_name)

deleted_count = self.processed_emails_service.cleanup_old_records(

def sync_folders(self) -> Tuple[bool, str]:
        """Sync IMAP folders with local database and track email processing status."""
        try:
            
            # Get folders from IMAP server
            imap_folders = self.get_folders()
            
            if not imap_folders:
                return False, "No folders found on IMAP server"
            
            # Deduplicate folders by name to prevent creating multiple entries for the same folder
            unique_folders = []
            seen_names = set()
            for imap_folder in imap_folders:
                folder_name = imap_folder['name']
                
                # Skip special folders that might not be needed
                if folder_name.lower() in ['inbox', 'sent', 'drafts', 'spam', 'trash']:
                    continue
                
                # Use case-insensitive comparison for deduplication
                folder_name_lower = folder_name.lower()
                if folder_name_lower not in seen_names:
                    unique_folders.append(imap_folder)
                    seen_names.add(folder_name_lower)
            
            # Process each unique folder
            synced_count = 0
            for imap_folder in unique_folders:
                
                # Handle nested folder names (convert slashes to underscores or keep as-is)
                # According to requirements, nested folders should be created with slashes in the name
                display_name = folder_name
                
                # Check if folder already exists
                existing_folder = Folder.query.filter_by(
                    name=display_name
                ).first()
                
                if not existing_folder:
                    # Create new folder
                    new_folder = Folder(
                        name=display_name,
                        rule_text=f"Auto-synced from IMAP folder: {folder_name}",
                        priority=0  # Default priority
                    )
                    db.session.add(new_folder)
                    synced_count += 1
                else:
                    # Get the list of email UIDs in this folder
                    email_uids = self.get_folder_email_uids(folder_name)
                    
                    # Sync with processed emails service to track which emails are pending
                    new_uids_count = self.processed_emails_service.sync_folder_emails(
                        email_uids=email_uids
                    )
                    
                    # Update folder with email counts
                    existing_folder.total_count = len(email_uids)
                    
                    # Update pending count based on processed emails service
                    pending_count = self.processed_emails_service.get_pending_count(folder_name)
                    existing_folder.pending_count = pending_count
                    
                    # Get the most recent emails for this folder
                    recent_emails = self.get_recent_emails(folder_name, 3)
                    existing_folder.recent_emails = recent_emails
                    
                    # Clean up records for emails that no longer exist
                    if new_uids_count > 0:
                        deleted_count = self.processed_emails_service.cleanup_old_records(
                            current_uids=email_uids
                        )
                        if deleted_count > 0:
                            print(f"    - Cleaned up {deleted_count} old email records")
            
            return True, f"Successfully synced {synced_count} folders"
            
            print(e)
            return False, f"Sync error: {str(e)}"

class IMAPService:
    
    def _connect(self):
        
        else:
    
    def test_connection(self) -> Tuple[bool, str]:
        try:
            
            # Login
            )
            
            # Logout
            
            print(e)
            print(e)
        finally:
                try:
                except:
                    pass
    
    def get_folders(self) -> List[Dict[str, str]]:
        try:
                return []
            
            # Login
            )
            
                return []

                else:
                
                })
            
            return []
        finally:
                try:
                except:
                    pass
    
    def get_folder_email_count(self, folder_name: str) -> int:
        try:
            
            # Login
            )
            
                return 0
            
                return 0
            
                try:
                except:
                    pass
            return 0
    
    def get_recent_emails(self, folder_name: str, limit: int = 3) -> List[Dict[str, any]]:
        try:
            
            # Login
            )
            
                return []
            
                return []
            
                    continue
                
                try:
                
                try:
                
                })
            
                try:
                except:
                    pass
            return []
    
    def get_folder_email_uids(self, folder_name: str) -> List[str]:
        try:
            
            # Login
            )
            
                return []
            
                return []
            
                try:
                except:
                    pass
            return []
    
        try:
            return []
    
    def get_email_headers(self, folder_name: str, email_uid: str) -> Dict[str, str]:
        try:
            
            # Login
            )
            
                return {}
            
                return {}
            
            headers = {
            }
            
                try:
                except:
                    pass
            return {}
    
    def sync_folders(self) -> Tuple[bool, str]:
        try:
            
                    continue
                
                ).first()
                
                    )
                else:
                    
                    )
                    
                        )
            
            print(e)

@main.route('/')
@login_required
def index():
    """Redirect to the folders index page."""
    # Get folders for the current authenticated user
    folders = Folder.query.filter_by(user_id=current_user.id).all()
    
    from flask import render_template
    return render_template('index.html',
                          folders=folders,
                          show_hidden=False)

def validate_password(password):
    """Validate password strength."""
    if len(password) < 8:
        return False, "Password must be at least 8 characters long"
    if not re.search(r'[A-Z]', password):
        return False, "Password must contain at least one uppercase letter"
    if not re.search(r'[a-z]', password):
        return False, "Password must contain at least one lowercase letter"
    if not re.search(r'\d', password):
        return False, "Password must contain at least one digit"
    return True, "Password is valid"

@auth.route('/login', methods=['GET', 'POST'])
def login():
    """Handle user login."""
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')
        remember = request.form.get('remember', False)
        
        # Validate input
        if not email:
            flash('Email is required', 'error')
            return render_template('auth/login.html')
        
        if not password:
            flash('Password is required', 'error')
        
        # Find user by email
        user = User.query.filter_by(email=email).first()
        
        if user and user.check_password(password):
            # Login successful
            login_user(user, remember=remember)
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('main.index'))
        else:
            # Login failed
            flash('Invalid email or password', 'error')
    
    return render_template('auth/login.html')

@auth.route('/signup', methods=['GET', 'POST'])
def signup():
    """Handle user registration."""
    
        first_name = request.form.get('first_name')
        last_name = request.form.get('last_name')
        confirm_password = request.form.get('confirm_password')
        
        errors = []
        
        if not first_name:
            errors.append('First name is required')
        elif len(first_name) > 50:
            errors.append('First name must be less than 50 characters')
        
        if not last_name:
            errors.append('Last name is required')
        elif len(last_name) > 50:
            errors.append('Last name must be less than 50 characters')
        
            errors.append('Email is required')
        elif not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', email):
            errors.append('Please enter a valid email address')
        elif User.query.filter_by(email=email).first():
            errors.append('Email already registered')
        
            errors.append('Password is required')
        else:
            is_valid, message = validate_password(password)
            if not is_valid:
                errors.append(message)
        
        if not confirm_password:
            errors.append('Please confirm your password')
        elif password != confirm_password:
            errors.append('Passwords do not match')
        
        if errors:
            return render_template('auth/signup.html', errors=errors,
                                 first_name=first_name, last_name=last_name, email=email)
        
        # Create new user
            first_name=first_name.strip(),
            last_name=last_name.strip(),
            email=email.strip()
        )
        user.set_password(password)
        
        # Login the new user
        login_user(user)
        
        flash('Account created successfully!', 'success')
    
    return render_template('auth/signup.html')

@auth.route('/logout')
def logout():
    """Handle user logout."""
    logout_user()
    flash('You have been logged out', 'info')
    return redirect(url_for('auth.login'))

        self.imap_service = IMAPService(user)

def process_user_emails(self) -> Dict[str, any]:
        """
        Process emails for a user according to their folder rules.
        
        Returns:
            Dictionary with processing results including success count, error count, and timing
        """
        result = {
            'success_count': 0,
            'processed_folders': [],
            'start_time': datetime.utcnow(),
            'end_time': None,
            'duration': None
        }
        
        try:
            # Get all folders with organize_enabled = True, ordered by priority (highest first)
            folders = Folder.query.filter_by(
                organize_enabled=True
            ).order_by(Folder.priority.desc()).all()
            
            if not folders:
                self.logger.info(f"No folders to process for user {self.user.email}")
                result['end_time'] = datetime.utcnow()
                result['duration'] = (result['end_time'] - result['start_time']).total_seconds()
                return result
            
            self.logger.info(f"Processing {len(folders)} folders for user {self.user.email}")
            
            # Process each folder according to priority
                try:
                    folder_result = self.process_folder_emails(folder)
                    result['success_count'] += folder_result['processed_count']
                    result['error_count'] += folder_result['error_count']
                    result['processed_folders'].append({
                        'folder_id': folder.id,
                        'folder_name': folder.name,
                        'processed_count': folder_result['processed_count'],
                        'error_count': folder_result['error_count']
                    })
                    
                    self.logger.info(f"Processed {folder_result['processed_count']} emails for folder {folder.name}")
                    
                except Exception as e:
                    self.logger.error(f"Error processing folder {folder.name}: {str(e)}")
                    result['error_count'] += 1
                    continue
            
            self.logger.error(f"Error in process_user_emails for user {self.user.email}: {str(e)}")
            result['error_count'] += 1
            
        finally:
            result['end_time'] = datetime.utcnow()
            result['duration'] = (result['end_time'] - result['start_time']).total_seconds()
            
        return result

def process_folder_emails(self, folder: Folder) -> Dict[str, any]:
        """
        Process emails for a specific folder according to its rules.
        
        Args:
            folder: The folder to process
        
        Returns:
            Dictionary with processing results for this folder
        """
        result = {
            'processed_count': 0,
            'folder_id': folder.id,
            'folder_name': folder.name
        }
        
        try:
            # Get pending emails for this folder
            pending_email_uids = self.processed_emails_service.get_pending_emails(folder.name)
            
            if not pending_email_uids:
                self.logger.info(f"No pending emails to process for folder {folder.name}")
            
            self.logger.info(f"Processing {len(pending_email_uids)} pending emails for folder {folder.name}")
            
            # Process emails in batches to manage system resources
            batch_size = 10  # Configurable batch size
            processed_batch_count = 0
            
            for i in range(0, len(pending_email_uids), batch_size):
                batch = pending_email_uids[i:i + batch_size]
                batch_result = self._process_email_batch(folder, batch)
                
                result['processed_count'] += batch_result['processed_count']
                result['error_count'] += batch_result['error_count']
                processed_batch_count += 1
                
                self.logger.info(f"Processed batch {i//batch_size + 1} for folder {folder.name}: {batch_result['processed_count']} success, {batch_result['error_count']} errors")
            
            # Update folder pending count after processing
            self._update_folder_counts(folder)
            
            self.logger.error(f"Error in process_folder_emails for folder {folder.name}: {str(e)}")
            
resp_code, content = self.imap_service.connection.select(folder.name)

headers = self.imap_service.get_email_headers(folder.name, email_uid)

destination_folder = self._evaluate_rules(headers, folder.rule_text)

count = self.processed_emails_service.mark_emails_processed(folder.name, processed_uids)

def _process_email_batch(self, folder: Folder, email_uids: List[str]) -> Dict[str, any]:
        """
        Process a batch of emails for a folder.
        
        Args:
            folder: The folder whose rules to apply
            email_uids: List of email UIDs to process
        
        Returns:
            Dictionary with processing results for this batch
        """
        result = {
        }
        
        try:
            self.imap_service._connect()
            
            # Login
            username = self.user.imap_config.get('username', '')
            password = self.user.imap_config.get('password', '')
            self.imap_service.connection.login(username, password)
            
            # Select the source folder
            resp_code, content = self.imap_service.connection.select(folder.name)
                raise Exception(f"Failed to select folder {folder.name}: {content}")
            
            # Process each email in the batch
            processed_uids = []
            for email_uid in email_uids:
                try:
                    # Get email headers to evaluate rules
                    headers = self.imap_service.get_email_headers(folder.name, email_uid)
                    
                    if not headers:
                        self.logger.warning(f"Could not get headers for email {email_uid} in folder {folder.name}")
                        result['error_count'] += 1
                        continue
                    
                    # Evaluate rules to determine destination
                    destination_folder = self._evaluate_rules(headers, folder.rule_text)
                    
                    if destination_folder and destination_folder != folder.name:
                        # Move email to destination folder
                        if self._move_email(email_uid, folder.name, destination_folder):
                            processed_uids.append(email_uid)
                            result['processed_count'] += 1
                        else:
                            self.logger.error(f"Failed to move email {email_uid} from {folder.name} to {destination_folder}")
                            result['error_count'] += 1
                    else:
                        # Mark as processed (no move needed)
                        processed_uids.append(email_uid)
                        result['processed_count'] += 1
                        
                    self.logger.error(f"Error processing email {email_uid}: {str(e)}")
                    continue
            
            # Mark processed emails in the database
            if processed_uids:
                count = self.processed_emails_service.mark_emails_processed(folder.name, processed_uids)
                if count != len(processed_uids):
                    self.logger.warning(f"Marked {count} emails as processed, but expected {len(processed_uids)}")
            
            self.imap_service.connection.close()
            self.imap_service.connection.logout()
            self.imap_service.connection = None
            
            self.logger.error(f"Error in _process_email_batch: {str(e)}")
            
            # Clean up connection if needed
            if self.imap_service.connection:
                try:
                    self.imap_service.connection.close()
                    self.imap_service.connection.logout()
                except:
                    pass
                self.imap_service.connection = None
            
headers: Dict[str, str], rule_text: Optional[str]) -> Optional[str]:

start_idx = rule_lower.find('move to') + 7  # length of 'move to'

def _evaluate_rules(self, headers: Dict[str, str], rule_text: Optional[str]) -> Optional[str]:
        """
        Evaluate rules against email headers to determine destination folder.
        
        Args:
            headers: Email headers (subject, from, to, date)
            rule_text: Rule text defining processing criteria
        
        Returns:
            Destination folder name or None if no move is needed
        """
        # This is a simplified implementation - in a real app, this would parse
        # the rule_text and evaluate it against the headers
        # For now, we'll implement a simple rule evaluation
        
        if not rule_text:
            return None
        
        # Simple example: if rule contains 'move to', extract destination
        # This should be replaced with proper rule parsing in production
        rule_lower = rule_text.lower()
        if 'move to' in rule_lower:
            try:
                start_idx = rule_lower.find('move to') + 7  # length of 'move to'
                # Extract folder name (everything after 'move to' until end or punctuation)
                destination = rule_text[start_idx:].strip()
                # Remove any trailing punctuation
                destination = destination.rstrip('.!,;:')
                return destination.strip()
            except:
                pass
        
        # Default: no move needed

email_uid: str, source_folder: str, destination_folder: str) -> bool:

result = self.imap_service.connection.copy(email_uid, destination_folder)

def _move_email(self, email_uid: str, source_folder: str, destination_folder: str) -> bool:
        """
        Move an email from source folder to destination folder.
        
        Args:
            email_uid: The UID of the email to move
            source_folder: The current folder name
            destination_folder: The destination folder name
        
        Returns:
            True if successful, False otherwise
        """
        try:
            # Copy email to destination folder
            result = self.imap_service.connection.copy(email_uid, destination_folder)
            if result[0] != 'OK':
                self.logger.error(f"Copy failed for email {email_uid}: {result[1]}")
                return False
            
            # Store the message ID to ensure it was copied
            # (In a real implementation, you might want to verify the copy)
            
            # Delete from source folder
            self.imap_service.connection.select(source_folder)
            result = self.imap_service.connection.store(email_uid, '+FLAGS', '\Deleted')
                self.logger.error(f"Mark as deleted failed for email {email_uid}: {result[1]}")
            
            # Expunge to permanently remove
            result = self.imap_service.connection.expunge()
                self.logger.error(f"Expunge failed for email {email_uid}: {result[1]}")
            
            return True
            
            self.logger.error(f"Error moving email {email_uid} from {source_folder} to {destination_folder}: {str(e)}")

pending_count = self.processed_emails_service.get_pending_count(folder.name)

total_count = self.imap_service.get_folder_email_count(folder.name)

def _update_folder_counts(self, folder: Folder) -> None:
        """
        Update folder counts after processing.
        
        Args:
            folder: The folder to update
        """
        try:
            # Update pending count
            pending_count = self.processed_emails_service.get_pending_count(folder.name)
            folder.pending_count = pending_count
            
            # Update total count (get from IMAP)
            total_count = self.imap_service.get_folder_email_count(folder.name)
            folder.total_count = total_count
            
            self.logger.error(f"Error updating folder counts for {folder.name}: {str(e)}")

class EmailProcessor:
    """
    Service class for processing emails in the background according to user-defined rules.
    Handles automated organization of emails based on folder configurations.
    """
    
    def process_user_emails(self) -> Dict[str, any]:
        """
        
        Returns:
        """
        result = {
        }
        
        try:
            
                try:
                    })
                    
                    continue
            
        finally:
            
    def process_folder_emails(self, folder: Folder) -> Dict[str, any]:
        """
        
        Args:
        
        Returns:
        """
        result = {
        }
        
        try:
            
    def _process_email_batch(self, folder: Folder, email_uids: List[str]) -> Dict[str, any]:
        """
        
        Args:
        
        Returns:
        """
        result = {
        }
        
        try:
            
            # Login
            
                try:
                    
                        continue
                    
                        else:
                    else:
                        
                    continue
            
                try:
                except:
                    pass
            
    def _evaluate_rules(self, headers: Dict[str, str], rule_text: Optional[str]) -> Optional[str]:
        """
        
        Args:
        
        Returns:
        """
        
            try:
            except:
                pass
        
    def _move_email(self, email_uid: str, source_folder: str, destination_folder: str) -> bool:
        """
        
        Args:
        
        Returns:
        """
        try:
            
    def _update_folder_counts(self, folder: Folder) -> None:
        """
        
        Args:
        """
        try:
            
bp = Blueprint('background_processing', __name__, url_prefix='/api/background')

@bp.route('/process-emails', methods=['POST'])
def trigger_email_processing():
    """
    Trigger immediate email processing for the current user.
    """
    try:
        processor = EmailProcessor(current_user)
        
        return jsonify({
            'success': True,
            'message': f"Processed {result['success_count']} emails successfully",
            'details': result
        }), 200
        
            'success': False,
            'error': str(e)
        }), 500

folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()

@bp.route('/process-folder/<int:folder_id>', methods=['POST'])
def trigger_folder_processing(folder_id):
    """
    Trigger email processing for a specific folder.
    """
    try:
        # Verify the folder belongs to the current user
        from app.models import Folder
        folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()
        
        if not folder:
            return jsonify({
                'success': False,
                'error': 'Folder not found or access denied'
            }), 404
        
        result = processor.process_folder_emails(folder)
        
            'message': f"Processed {result['processed_count']} emails for folder {folder.name}",
        }), 200
        
        }), 500

pending_uids = processed_emails_service.get_pending_emails(folder.name)

response = make_response(render_template('partials/pending_emails_dialog.html', 

@emails_bp.route('/api/folders/<int:folder_id>/pending-emails', methods=['GET'])
def get_pending_emails(folder_id):
    """Get pending emails for a folder with email metadata."""
    try:
        # Find the folder and ensure it belongs to the current user
        
            return jsonify({'error': 'Folder not found'}), 404
        
        # Get IMAP service to fetch email data
        imap_service = IMAPService(current_user)
        
        # Get all email UIDs in the folder
        all_uids = imap_service.get_folder_email_uids(folder.name)
        
        # Get processed emails service
        processed_emails_service = ProcessedEmailsService(current_user)
        
        # Get pending email UIDs
        pending_uids = processed_emails_service.get_pending_emails(folder.name)
        
        # Get email headers for pending emails
        pending_emails = []
        for uid in pending_uids:
            headers = imap_service.get_email_headers(folder.name, uid)
            if headers:
                # Add UID to the response
                email_data = {
                    'uid': uid,
                    'subject': headers.get('subject', 'No Subject'),
                    'date': headers.get('date', ''),
                    'from': headers.get('from', ''),
                    'to': headers.get('to', ''),
                    'message_id': headers.get('message_id', '')
                }
                pending_emails.append(email_data)
        
        # Return the pending emails in a dialog format
        response = make_response(render_template('partials/pending_emails_dialog.html', 
                                               folder=folder, 
                                               pending_emails=pending_emails))
        response.headers['HX-Trigger'] = 'open-modal'
        return response
        
        logging.exception("Error getting pending emails: %s", e)
        return jsonify({'error': 'An unexpected error occurred'}), 500

success = processed_emails_service.mark_email_processed(folder.name, email_uid)

pending_count = processed_emails_service.get_pending_count(folder.name)

response = make_response(render_template('partials/pending_emails_updated.html', 

@emails_bp.route('/api/folders/<int:folder_id>/emails/<email_uid>/process', methods=['POST'])
def mark_email_processed(folder_id, email_uid):
    """Mark a specific email as processed."""
    try:
        
        # Mark email as processed
        success = processed_emails_service.mark_email_processed(folder.name, email_uid)
        
        if success:
            # Get updated counts
            pending_count = processed_emails_service.get_pending_count(folder.name)
            
            # Update the folder's count based on folder type
            if folder.folder_type == 'tidy':
            elif folder.folder_type == 'destination':
                # For destination folders, update emails_count
                folder.emails_count = pending_count
            
            # Return updated dialog body
            response = make_response(render_template('partials/pending_emails_updated.html', 
                                                   folder=folder, 
                                                   uid=email_uid))
            response.headers['HX-Trigger'] = 'close-modal'
            return response
        else:
            return jsonify({'error': 'Failed to mark email as processed'}), 500
        
        logging.exception("Error marking email as processed: %s", e)

new_emails_count = processed_emails_service.sync_folder_emails(folder.name, email_uids)

@emails_bp.route('/api/folders/<int:folder_id>/sync-emails', methods=['POST'])
def sync_folder_emails(folder_id):
    """Sync emails for a specific folder with processed email tracking."""
    try:
        
        # Get IMAP service to fetch email UIDs
        
        print(imap_service)
        email_uids = imap_service.get_folder_email_uids(folder.name)
        
        if not email_uids:
            return jsonify({'error': 'No emails found in folder'}), 200
        
        # Sync emails with the processed emails service
        new_emails_count = processed_emails_service.sync_folder_emails(folder.name, email_uids)
        
        # Get updated counts
        pending_count = processed_emails_service.get_pending_count(folder.name)
        
        # Update the folder's counts based on folder type
        if folder.folder_type == 'tidy':
            folder.total_count = len(email_uids)
        elif folder.folder_type == 'destination':
            # For destination folders, update emails_count
            folder.emails_count = pending_count  # Using pending_count as emails_count for destination folders
        
        # Return success response with updated counts
            'message': f'Synced {new_emails_count} new emails',
            'pending_count': pending_count,
            'total_count': len(email_uids)
        })
        
        logging.exception("Error syncing folder emails: %s", e)

processed_count = processed_emails_service.mark_emails_processed(folder.name, email_uids)

@emails_bp.route('/api/folders/<int:folder_id>/process-emails', methods=['POST'])
def process_folder_emails(folder_id):
    """Process multiple emails in a folder (mark as processed)."""
    try:
        
        # Get email UIDs from form data
        email_uids = request.form.getlist('email_uids')
        
            return jsonify({'error': 'No email UIDs provided'}), 400
        
        # Mark emails as processed
        processed_count = processed_emails_service.mark_emails_processed(folder.name, email_uids)
        
        if processed_count > 0:
            
                'success': True,
                'message': f'Processed {processed_count} emails',
                'pending_count': pending_count
            })
        else:
            return jsonify({'error': 'No emails were processed'}), 400
        
        logging.exception("Error processing folder emails: %s", e)

@folders_bp.route('/')
    """Main page showing all folders."""
    
response = make_response(render_template('partials/folder_modal.html', folder_data={'rule_text': folder.rule_text,

@folders_bp.route('/api/folders/new', methods=['GET'])
def new_folder_modal():
    """Return the add folder modal."""
    response = make_response(render_template('partials/folder_modal.html', folder_data={'rule_text': folder.rule_text,
                                                               'show_ai_rules': True,
                                                              'errors': None }))
    response.headers['HX-Trigger'] = 'open-modal'
    return response

response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority, 

folder_type = 'tidy' if name.strip().lower() == 'inbox' else 'destination'

@folders_bp.route('/api/folders', methods=['POST'])
def add_folder():
    """Add a new folder."""
    try:
        # Get form data instead of JSON
        name = request.form.get('name')
        rule_text = request.form.get('rule_text')
        priority = request.form.get('priority')
        
        # Server-side validation
        errors = {}
        if not name or not name.strip():
            errors['name'] = 'Folder name is required'
        elif len(name.strip()) < 3:
            errors['name'] = 'Folder name must be at least 3 characters'
        elif len(name.strip()) > 50:
            errors['name'] = 'Folder name must be less than 50 characters'
            
        if not rule_text or not rule_text.strip():
            errors['rule_text'] = 'Rule text is required'
        elif len(rule_text.strip()) < 10:
            errors['rule_text'] = 'Rule text must be at least 10 characters'
        elif len(rule_text.strip()) > 200:
            errors['rule_text'] = 'Rule text must be less than 200 characters'
            
        # If there are validation errors, return the modal with errors
        if errors:
            response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority, 
                                                     folder_data={'rule_text': '',
            response.headers['HX-Retarget'] = '#folder-modal'
            response.headers['HX-Reswap'] = 'outerHTML'
            
        # Create new folder for the current user
        # Default to 'destination' type for manually created folders
        folder_type = 'tidy' if name.strip().lower() == 'inbox' else 'destination'
        
        # If folder_type is 'ignore', reset emails_count to 0
        if folder_type == 'ignore':
            emails_count = 0
        
            user_id=current_user.id,
            name=name.strip(),
            rule_text=rule_text.strip(),
            priority=int(priority) if priority else 0,
            folder_type=folder_type,
            emails_count=0 if folder_type == 'ignore' else None
        )
        
        response = make_response('')
        response.headers['HX-Trigger'] = 'close-modal, folder-list-invalidated'
        response.status_code = 201
        
        # Print unhandled exceptions to the console as required
        logging.exception("Error adding folder: %s", e)
        db.session.rollback()
        # Return error in modal
        errors = {'general': 'An unexpected error occurred. Please try again.'}
        # Get updated lists of folders by type for error fallback
        
        response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority))
        response.headers['HX-Retarget'] = '#folder-modal'
        response.headers['HX-Reswap'] = 'outerHTML'

@folders_bp.route('/api/folders/<folder_id>', methods=['DELETE'])
def delete_folder(folder_id):
    """Delete a folder."""
    try:
        # Find the folder by ID and ensure it belongs to the current user
        
            # Folder not found
            response = make_response('')
            response.headers['HX-Trigger'] = 'folder-list-invalidated'
            
        # Delete all associated processed emails first
        from app.models import ProcessedEmail
        ProcessedEmail.query.filter_by(folder_id=folder.id).delete()
        
        # Delete the folder
        db.session.delete(folder)
        
        response.headers['HX-Trigger'] = 'folder-list-invalidated'
        
        logging.exception("Error deleting folder: %s", e)
        # Return the folders list unchanged

@folders_bp.route('/api/folders/<folder_id>/type', methods=['PUT'])
def update_folder_type(folder_id):
    """Update the type of a folder."""
    try:
        
        # Get the new folder type from form data
        new_folder_type = request.form.get('folder_type')
        
        # Validate the folder type
        if new_folder_type not in ['tidy', 'destination', 'ignore']:
            return jsonify({'error': 'Invalid folder type'}), 400
            
        # If changing to 'ignore', reset the emails_count to 0
        if new_folder_type == 'ignore' and folder.folder_type != 'ignore':
            folder.emails_count = 0
        
        # Update the folder type
        folder.folder_type = new_folder_type
        
        logging.exception("Error updating folder type: %s", e)

response = make_response(render_template('partials/folder_modal.html', folder=folder,

@folders_bp.route('/api/folders/<folder_id>/edit', methods=['GET'])
def edit_folder_modal(folder_id):
    """Return the edit folder modal with folder data."""
    try:
        
        # Return the edit folder modal with folder data
        response = make_response(render_template('partials/folder_modal.html', folder=folder,
                                                 folder_data={'rule_text': folder.rule_text,
        
        logging.exception("Error getting folder for edit: %s", e)
        return jsonify({'error': 'Error retrieving folder'}), 500

@folders_bp.route('/api/folders/<folder_id>', methods=['PUT'])
def update_folder(folder_id):
    """Update an existing folder."""
    try:
        
            response.headers['HX-Trigger'] = 'close-modal, folder-list-invalidated'
 
        # Get form data
        
        errors = {}
            
        if errors:
            response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority,
                                                     folder_data={'rule_text': folder.rule_text,
    
        # Update folder
        folder.name = name.strip()
        folder.rule_text = rule_text.strip()
        folder.priority = int(priority) if priority else 0
        
        # Check if folder type is being changed to 'ignore'
        old_folder_type = folder.folder_type
        folder.folder_type = 'tidy' if name.strip().lower() == 'inbox' else 'destination'
        
        # If changing to 'ignore', reset emails_count to 0
        if folder.folder_type == 'ignore' and old_folder_type != 'ignore':
        
        logging.exception("Error updating folder: %s", e)
        response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority,

is_event_triggered = request.headers.get('HX-Trigger') == 'folder-list-invalidated'

show_hidden = request.args.get('show_hidden', 'off').lower() == 'on'

response = make_response(render_template('partials/folders_to_tidy_section.html', folders=folders, show_hidden=show_hidden))

@folders_bp.route('/api/folders', methods=['GET'])
def get_folders():
    """Get folders with optional filtering."""
    # Check if this is an event-triggered request
    is_event_triggered = request.headers.get('HX-Trigger') == 'folder-list-invalidated'
    
    # Get filter parameter from query string
    filter_type = request.args.get('filter', 'all')
    folder_type = request.args.get('type', None)
    show_hidden = request.args.get('show_hidden', 'off').lower() == 'on'
    
    # For event-triggered requests, maintain current filter state
    if is_event_triggered:
        # If this is an event trigger, we need to preserve the current filter state
        # The event listener will have the current state in the DOM
        # So we just return the full list with current show_hidden state
        folders = Folder.query.filter_by(user_id=current_user.id).all()
        return render_template('partials/folders_list.html', folders=folders, show_hidden=show_hidden)
    
    if folder_type:
        # Filter by folder type
        folders = Folder.query.filter_by(user_id=current_user.id, folder_type=folder_type).all()
    elif filter_type == 'high':
        # Get high priority folders (priority = 1)
        folders = Folder.query.filter_by(user_id=current_user.id, priority=1).all()
    elif filter_type == 'normal':
        # Get normal priority folders (priority = 0 or not set)
        folders = Folder.query.filter_by(user_id=current_user.id).filter(Folder.priority != 1).all()
    else:
        # Get all folders
    
    if folder_type == 'tidy':
        response = make_response(render_template('partials/folders_to_tidy_section.html', folders=folders, show_hidden=show_hidden))
        response.headers["HX-Retarget"] = "#folders-to-tidy"
    elif folder_type == 'destination':
        response = make_response(render_template('partials/destination_folders_section.html', folders=folders, show_hidden=show_hidden))
        response.headers["HX-Retarget"] = "#destination-folders"
    elif folder_type == 'ignore':
        response = make_response(render_template('partials/hidden_folders_section.html', folders=folders, show_hidden=show_hidden))
        response.headers["HX-Retarget"] = "#hidden-folders"
        
        # Show or hide the hidden folders section based on the show_hidden parameter
        if show_hidden:
            response.headers['HX-Trigger'] = 'show-hidden'
        else:
            response.headers['HX-Trigger'] = 'hide-hidden'
            
    else:

rule_type = request.form.get('rule_type', 'single')  # 'single' or 'multiple'

cache_key = AIRuleCache.generate_cache_key(folder_name, folder_type, rule_type, rule_text)

rules, metadata = ai_service.generate_multiple_rules(folder_name, folder_type, rule_text)

metadata = ai_service.generate_multiple_rules(folder_name, folder_type, rule_text)

fallback_rule = ai_service.get_fallback_rule(folder_name, folder_type)

@folders_bp.route('/api/folders/generate-rule', methods=['POST'])
def generate_rule():
    """Generate an email organization rule using AI."""
    try:
        folder_name = request.form.get('name', '').strip()
        folder_type = request.form.get('folder_type', 'destination')
        rule_type = request.form.get('rule_type', 'single')  # 'single' or 'multiple'
        rule_text = request.form.get('rule_text', '')
        
        # Validate inputs
        if not folder_name:
            return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Folder name is required'})
            
        if folder_type not in ['destination', 'tidy', 'ignore']:
            return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Invalid folder type'})
            
        if rule_type not in ['single', 'multiple']:
            return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Invalid rule type'})
            
        # Check cache first
        cache_key = AIRuleCache.generate_cache_key(folder_name, folder_type, rule_type, rule_text)
        cached_rule = AIRuleCache.query.filter_by(
            cache_key=cache_key,
        ).first()
        
        if cached_rule and not cached_rule.is_expired():
            # Return cached result
            result = {
                'cached': True,
                'rule': cached_rule.rule_text,
                'metadata': cached_rule.rule_metadata,
                'quality_score': cached_rule.rule_metadata.get('quality_score', 0) if cached_rule.rule_metadata else 0
            }
            return render_template('partials/ai_rule_result.html', result=result)
        
        # Generate new rule using AI service
        # Only support multiple rules now
        rules, metadata = ai_service.generate_multiple_rules(folder_name, folder_type, rule_text)
        
        if rules is None:
            # AI service failed, return fallback
            fallback_rule = ai_service.get_fallback_rule(folder_name, folder_type)
            result = {
                'fallback': True,
                'rules': [{'text': fallback_rule, 'quality_score': 50}],
                'message': 'AI service unavailable, using fallback rule'
            }
            
        # Cache the first rule as representative
        expires_at = datetime.utcnow() + timedelta(hours=1)
            folder_name=folder_name,
            rule_text=rules[0]['text'] if rules else '',
            rule_metadata=metadata,
            expires_at=expires_at
        )
        
        result = {
            'rules': rules,
            'metadata': metadata
        }
        return render_template('partials/ai_rule_result.html', result=result)
            
        logging.exception("Error generating rule: %s", e)
        return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'An unexpected error occurred'})

quality_assessment = ai_service.assess_rule_quality(rule_text, folder_name, folder_type)

@folders_bp.route('/api/folders/assess-rule', methods=['POST'])
def assess_rule():
    """Assess the quality of an email organization rule."""
    try:
        rule_text = request.form.get('rule_text', '').strip()
        folder_name = request.form.get('folder_name', '').strip()
        
            return render_template('partials/ai_rule_result.html', result={'success': False, 'error': 'Rule text is required'})
            
        # Assess rule quality
        quality_assessment = ai_service.assess_rule_quality(rule_text, folder_name, folder_type)
        
        result = {
            'assessment': quality_assessment,
            'rule': rule_text,
            'quality_score': quality_assessment['score']
        }
        
        logging.exception("Error assessing rule: %s", e)

response = make_response(render_template('partials/imap_config_modal.html',

@imap_bp.route('/api/imap/config', methods=['GET'])
def imap_config_modal():
    """Return the IMAP configuration modal."""
    # Pass existing IMAP config to the template if it exists
    response = make_response(render_template('partials/imap_config_modal.html',
                                           server=current_user.imap_config.get('server') if current_user.imap_config else None,
                                           port=current_user.imap_config.get('port') if current_user.imap_config else None,
                                           username=current_user.imap_config.get('username') if current_user.imap_config else None,
                                           password=current_user.imap_config.get('password') if current_user.imap_config else None,
                                           use_ssl=current_user.imap_config.get('use_ssl', True) if current_user.imap_config else True))

existing_folders_dict = {folder.name: folder for folder in existing_folders}

response = make_response(render_template('partials/folder_selection_modal.html', folders=unique_folders))

@imap_bp.route('/api/imap/folders/modal', methods=['GET'])
def imap_folders_modal():
    """Return the folder selection modal after successful IMAP connection."""
    try:
        if not current_user.imap_config:
            return jsonify({'error': 'No IMAP configuration found. Please configure IMAP first.'}), 400
        
        # Test connection first
        
        # Get folders from IMAP server
        imap_folders = imap_service.get_folders()
        
        if not imap_folders:
            return jsonify({'error': 'No folders found on IMAP server'}), 400
        
        # Get all existing folders in a single query
        existing_folders = Folder.query.filter(
            Folder.user_id == current_user.id
        ).all()
        
        # Create a dictionary for quick lookup
        existing_folders_dict = {folder.name: folder for folder in existing_folders}
        
        # Process folders
        unique_folders = []
        for imap_folder in imap_folders:
            folder_name = imap_folder['name'].strip()
            
            # Skip special folders that might not be needed
            if folder_name.lower() in ['sent', 'drafts', 'spam', 'trash']:
                continue
            
            # Check if this folder already exists in the database
            existing_folder = existing_folders_dict.get(folder_name)
            
            # Add folder type and subscribed status if it exists
            if existing_folder:
                imap_folder['folder_type'] = existing_folder.folder_type
                imap_folder['subscribed'] = True
                imap_folder['selected'] = True
            else:
                # Set default folder type
                imap_folder['folder_type'] = 'tidy' if folder_name.lower() == 'inbox' else 'destination'
                imap_folder['subscribed'] = False
            
            unique_folders.append(imap_folder)
        
        print(unique_folders, existing_folders_dict)
        # Return the folder selection modal
        response = make_response(render_template('partials/folder_selection_modal.html', folders=unique_folders))
        response.headers['hx-retarget'] = "#modal-holder"
            
        logging.exception("Error getting IMAP folders modal: %s", e)
        print(e)
        return jsonify({'error': 'An unexpected error occurred while fetching folders'}), 500

response = make_response(render_template('partials/imap_config_modal.html', errors=errors, server=server, port=port, username=username, use_ssl=use_ssl))

@imap_bp.route('/api/imap/test', methods=['POST'])
def test_imap_connection():
    """Test IMAP connection with provided configuration."""
    try:
        server = request.form.get('server')
        port = request.form.get('port')
        username = request.form.get('username')
        use_ssl = request.form.get('use_ssl') == 'on'
        
        # Validate required fields
        errors = {}
        if not server:
            errors['server'] = 'Server is required'
        if not port:
            errors['port'] = 'Port is required'
        elif not port.isdigit():
            errors['port'] = 'Port must be a number'
        if not username:
            errors['username'] = 'Username is required'
            errors['password'] = 'Password is required'
        
        if errors:
            response = make_response(render_template('partials/imap_config_modal.html', errors=errors, server=server, port=port, username=username, use_ssl=use_ssl))
            response.headers['HX-Retarget'] = '#imap-modal'
        
        # Store configuration temporarily for testing
        test_config = {
            'server': server,
            'port': int(port),
            'username': username,
            'password': password,
            'use_ssl': False,
            'use_tls': False,
            'connection_timeout': 30
        }
        
        # Test connection
        temp_user = type('User', (), {'imap_config': test_config})()
        imap_service = IMAPService(temp_user)
        print(temp_user, test_config)
        
            # Save configuration to user's profile
            current_user.imap_config = test_config
            
            # Redirect to folder selection modal after successful connection
            return imap_folders_modal()
            #response.headers['HX-Trigger'] = 'open-modal'
        else:
            response = make_response(render_template('partials/imap_config_modal.html',
                                                   errors={'general': message}, server=server, port=port, username=username, use_ssl=use_ssl))
        
        logging.exception("Error testing IMAP connection: %s", e)
        print(e)
        response = make_response(render_template('partials/imap_config_modal.html', errors=errors, server=server, port=port, username=username, use_ssl=use_ssl))
        response.headers['HX-Retarget'] = '#imap-modal'

@imap_bp.route('/api/imap/folders', methods=['GET'])
def get_imap_folders():
    """Get folders from IMAP server without creating database records."""
    try:
        
        # Deduplicate folders by name to prevent creating multiple entries for the same folder
        seen_names = set()
            folder_name = imap_folder['name']
            
                continue
            
            # Use case-insensitive comparison for deduplication
            folder_name_lower = folder_name.lower()
            if folder_name_lower not in seen_names:
                unique_folders.append(imap_folder)
                seen_names.add(folder_name_lower)
        
        return jsonify({'folders': unique_folders})
            
        logging.exception("Error getting IMAP folders: %s", e)
        print(e)

folder_type = request.form.get(type_key, 'destination')  # Default to 'destination'

new_emails_count = processed_emails_service.sync_folder_emails(display_name, email_uids)

pending_count = processed_emails_service.get_pending_count(display_name)

@imap_bp.route('/api/imap/sync-selected', methods=['POST'])
def sync_selected_folders():
    """Sync only the selected folders from IMAP server with processed email tracking."""
    try:
        
        # Get selected folder names and types from form data
        selected_folders = {}
        for key, value in request.form.items():
            if key.startswith('folder_'):
                folder_name = value
                # Check if there's a corresponding folder type
                type_key = f'folder_type_{key.split("_")[1]}'
                folder_type = request.form.get(type_key, 'destination')  # Default to 'destination'
                selected_folders[folder_name] = folder_type
        
        if not selected_folders:
            return jsonify({'error': 'No folders selected'}), 400
        
                continue
            
            # Only include folders that were selected by the user
            if folder_name in selected_folders:
                    # Add the selected folder type
                    imap_folder['selected_type'] = selected_folders[folder_name]
        
        # Process each selected unique folder
        synced_count = 0
        
        # Create a list of folders to process
        folders_to_process = []
        
        for imap_folder in unique_folders:
            folder_type = imap_folder.get('selected_type', 'destination')  # Get the selected type
            
            # Handle nested folder names (convert slashes to underscores or keep as-is)
            # According to requirements, nested folders should be created with slashes in the name
            display_name = folder_name
            
            # Check if folder already exists
            existing_folder = Folder.query.filter_by(
                user_id=current_user.id,
                name=display_name
            ).first()
            
            if not existing_folder:
                # Create new folder with the selected type
                new_folder = Folder(
                    user_id=current_user.id,
                    name=display_name,
                    rule_text=f"Auto-synced from IMAP folder: {folder_name}",
                    priority=0,  # Default priority
                    folder_type=folder_type
                )
                db.session.add(new_folder)
                synced_count += 1
                folders_to_process.append(new_folder)
            else:
                # Update existing folder with the selected type and email counts
                existing_folder.folder_type = folder_type
                
                # Get all email UIDs in this folder
                email_uids = imap_service.get_folder_email_uids(folder_name)
                
                # Sync with processed emails service
                new_emails_count = processed_emails_service.sync_folder_emails(display_name, email_uids)
                print("NEW", new_emails_count)
                
                # Update counts
                pending_count = processed_emails_service.get_pending_count(display_name)
                existing_folder.pending_count = pending_count
                existing_folder.total_count = len(email_uids)
                
                # Get the most recent emails for this folder
                recent_emails = imap_service.get_recent_emails(folder_name, 3)
                existing_folder.recent_emails = recent_emails
                
                folders_to_process.append(existing_folder)
        
        # Just trigger the folder list update and close the modal
            
        logging.exception("Error syncing selected IMAP folders: %s", e)
        print(e)