#!/usr/bin/env python3
"""
BMC Redfish Client CLI Tool
"""

import argparse
import json
import sys
import time
import os
import concurrent.futures
from datetime import datetime
from cumulus.bmc_redfish import BMC


def load_config_file(config_path='/etc/cumulus/bmc.conf'):
    """
    Load BMC configuration from file.
    Returns a dict with BMC_HOST, BMC_USER, BMC_PASS if found.
    """
    config = {}
    
    # Also check home directory for user-specific config
    home_config = os.path.expanduser('~/.bmc.conf')
    config_paths = [home_config, config_path]
    
    for path in config_paths:
        if os.path.exists(path):
            try:
                with open(path, 'r', encoding='utf-8') as f:
                    for line in f:
                        line = line.strip()
                        if line and not line.startswith('#'):
                            if '=' in line:
                                key, value = line.split('=', 1)
                                key = key.strip()
                                value = value.strip()
                                if key in ['BMC_HOST', 'BMC_USER', 'BMC_PASS', 'BMC_INTERFACE']:
                                    config[key] = value
            except Exception:
                # Silently ignore if we can't read the config file
                pass
    
    return config


def get_default_values():
    """
    Get default values from environment variables and config files.
    Precedence: Environment variables > Config file > Built-in defaults
    """
    defaults = {
        'host': None,
        'username': 'admin',
        'password': None,
        'interface': None
    }
    
    # Load from config file first
    config = load_config_file()
    if 'BMC_HOST' in config:
        defaults['host'] = config['BMC_HOST']
    if 'BMC_USER' in config:
        defaults['username'] = config['BMC_USER']
    if 'BMC_PASS' in config:
        defaults['password'] = config['BMC_PASS']
    if 'BMC_INTERFACE' in config:
        defaults['interface'] = config['BMC_INTERFACE']
    
    # Environment variables override config file
    if 'BMC_HOST' in os.environ:
        defaults['host'] = os.environ['BMC_HOST']
    if 'BMC_USER' in os.environ:
        defaults['username'] = os.environ['BMC_USER']
    if 'BMC_PASS' in os.environ:
        defaults['password'] = os.environ['BMC_PASS']
    if 'BMC_INTERFACE' in os.environ:
        defaults['interface'] = os.environ['BMC_INTERFACE']
    
    return defaults


def format_system_info(info):
    """Format system information in a user-friendly way"""
    output = []
    output.append("=== System Information ===")
    
    # Basic system info
    if 'Name' in info:
        output.append(f"System Name: {info['Name']}")
    if 'Manufacturer' in info:
        output.append(f"Manufacturer: {info['Manufacturer']}")
    if 'Model' in info:
        output.append(f"Model: {info['Model']}")
    if 'SerialNumber' in info:
        output.append(f"Serial Number: {info['SerialNumber']}")
    if 'SKU' in info:
        output.append(f"SKU: {info['SKU']}")
    
    # Status
    if 'Status' in info:
        status = info['Status']
        if 'State' in status:
            output.append(f"State: {status['State']}")
        if 'Health' in status:
            output.append(f"Health: {status['Health']}")
    
    # Power State
    if 'PowerState' in info:
        output.append(f"Power State: {info['PowerState']}")
    
    # BIOS Version
    if 'BiosVersion' in info:
        output.append(f"BIOS Version: {info['BiosVersion']}")
    
    # Memory
    if 'MemorySummary' in info:
        mem = info['MemorySummary']
        if 'TotalSystemMemoryGiB' in mem:
            output.append(f"Total Memory: {mem['TotalSystemMemoryGiB']} GiB")
    
    # Processors
    if 'ProcessorSummary' in info:
        proc = info['ProcessorSummary']
        if 'Count' in proc:
            output.append(f"Processor Count: {proc['Count']}")
        if 'Model' in proc:
            output.append(f"Processor Model: {proc['Model']}")
    
    return '\n'.join(output)


def format_thermal_info(info):
    """Format thermal information in a user-friendly way"""
    output = []
    output.append("=== Thermal Information ===")
    
    # For newer ThermalSubsystem format
    if '@odata.type' in info and 'ThermalSubsystem' in info['@odata.type']:
        if 'Status' in info:
            status = info['Status']
            if 'State' in status:
                output.append(f"Thermal Subsystem State: {status['State']}")
            if 'Health' in status:
                output.append(f"Thermal Subsystem Health: {status['Health']}")
                
        # Links to specific resources
        if 'Fans' in info:
            output.append(f"Fans endpoint: {info['Fans'].get('@odata.id', 'N/A')}")
        if 'ThermalMetrics' in info:
            output.append(f"Thermal metrics endpoint: {info['ThermalMetrics'].get('@odata.id', 'N/A')}")
    
    # For older Thermal format with inline data
    elif 'Temperatures' in info:
        output.append("\nTemperature Sensors:")
        for temp in info['Temperatures']:
            name = temp.get('Name', 'Unknown')
            reading = temp.get('ReadingCelsius', 'N/A')
            status = temp.get('Status', {}).get('Health', 'Unknown')
            output.append(f"  - {name}: {reading}°C (Health: {status})")
    
    # Fans information
    if 'Fans' in info and isinstance(info['Fans'], list):
        output.append("\nFan Status:")
        for fan in info['Fans']:
            name = fan.get('Name', 'Unknown')
            reading = fan.get('Reading', 'N/A')
            units = fan.get('ReadingUnits', 'RPM')
            status = fan.get('Status', {}).get('Health', 'Unknown')
            output.append(f"  - {name}: {reading} {units} (Health: {status})")
    
    return '\n'.join(output)


def format_power_info(info):
    """Format power information in a user-friendly way"""
    output = []
    output.append("=== Power Information ===")
    
    # For newer PowerSubsystem format
    if '@odata.type' in info and 'PowerSubsystem' in info['@odata.type']:
        if 'Status' in info:
            status = info['Status']
            if 'State' in status:
                output.append(f"Power Subsystem State: {status['State']}")
            if 'Health' in status:
                output.append(f"Power Subsystem Health: {status['Health']}")
                
        # Power allocation
        if 'Allocation' in info:
            alloc = info['Allocation']
            if 'AllocatedWatts' in alloc:
                output.append(f"Allocated Power: {alloc['AllocatedWatts']} W")
            if 'RequestedWatts' in alloc:
                output.append(f"Requested Power: {alloc['RequestedWatts']} W")
                
        # Links to specific resources
        if 'PowerSupplies' in info:
            output.append(f"Power supplies endpoint: {info['PowerSupplies'].get('@odata.id', 'N/A')}")
    
    # For older Power format with inline data
    elif 'PowerControl' in info:
        output.append("\nPower Control:")
        for pc in info['PowerControl']:
            name = pc.get('Name', 'System Power')
            consumed = pc.get('PowerConsumedWatts', 'N/A')
            limit = pc.get('PowerLimit', {}).get('LimitInWatts', 'N/A')
            output.append(f"  - {name}:")
            output.append(f"    Consumed: {consumed} W")
            output.append(f"    Limit: {limit} W")
    
    # Power supplies
    if 'PowerSupplies' in info and isinstance(info['PowerSupplies'], list):
        output.append("\nPower Supplies:")
        for ps in info['PowerSupplies']:
            name = ps.get('Name', 'Unknown')
            model = ps.get('Model', 'N/A')
            status = ps.get('Status', {}).get('Health', 'Unknown')
            power_capacity = ps.get('PowerCapacityWatts', 'N/A')
            output.append(f"  - {name}:")
            output.append(f"    Model: {model}")
            output.append(f"    Capacity: {power_capacity} W")
            output.append(f"    Health: {status}")
    
    # Voltages
    if 'Voltages' in info and isinstance(info['Voltages'], list):
        output.append("\nVoltage Sensors:")
        for volt in info['Voltages']:
            name = volt.get('Name', 'Unknown')
            reading = volt.get('ReadingVolts', 'N/A')
            status = volt.get('Status', {}).get('Health', 'Unknown')
            output.append(f"  - {name}: {reading} V (Health: {status})")
    
    return '\n'.join(output)


def get_system_info(bmc, debug=False, json_output=False):
    """Display system information"""
    ret, info = bmc.get_system_info()
    if ret == 0:
        if json_output or debug:
            print(json.dumps(info, indent=2))
        else:
            print(format_system_info(info))
    else:
        if json_output:
            error_data = {"error": f"Failed to get system info", "code": ret}
            print(json.dumps(error_data, indent=2))
        else:
            print(f"Failed to get system info: {info}")
        return 1
    return 0


def get_thermal_info(bmc, debug=False, json_output=False):
    """Display thermal information"""
    ret, info = bmc.get_thermal_info()
    if ret == 0:
        if json_output or debug:
            print(json.dumps(info, indent=2))
        else:
            print(format_thermal_info(info))
    else:
        if json_output:
            error_data = {"error": f"Failed to get thermal info", "code": ret}
            print(json.dumps(error_data, indent=2))
        else:
            print(f"Failed to get thermal info: Error code {ret}")
        return 1
    return 0


def get_power_info(bmc, debug=False, json_output=False):
    """Display power information"""
    ret, info = bmc.get_power_info()
    if ret == 0:
        if json_output or debug:
            print(json.dumps(info, indent=2))
        else:
            print(format_power_info(info))
    else:
        if json_output:
            error_data = {"error": f"Failed to get power info", "code": ret}
            print(json.dumps(error_data, indent=2))
        else:
            print(f"Failed to get power info: Error code {ret}")
        return 1
    return 0


def format_firmware_inventory(inventory):
    """Format firmware inventory in a user-friendly way"""
    output = []
    output.append("=== Firmware Inventory ===")
    
    if inventory:
        output.append("Available firmware components:")
        for fw in inventory:
            # Extract component name from the URI if possible
            if '@odata.id' in fw:
                component = fw['@odata.id'].split('/')[-1]
            else:
                component = fw
            output.append(f"  - {component}")
    else:
        output.append("No firmware components found")
    
    return '\n'.join(output)


def get_firmware_inventory(bmc, debug=False, json_output=False):
    """Display firmware inventory"""
    ret, inventory = bmc.get_firmware_inventory()
    if ret == 0:
        if json_output or debug:
            print(json.dumps(inventory, indent=2))
        else:
            print(format_firmware_inventory(inventory))
    else:
        if json_output:
            error_data = {"error": f"Failed to get firmware inventory", "code": ret}
            print(json.dumps(error_data, indent=2))
        else:
            print(f"Failed to get firmware inventory: Error code {ret}")
        return 1
    return 0


def format_firmware_detail(detail):
    """Format firmware component details in a user-friendly way"""
    output = []
    output.append("=== Firmware Component Details ===")
    
    if 'Id' in detail:
        output.append(f"Component ID: {detail['Id']}")
    if 'Name' in detail:
        output.append(f"Name: {detail['Name']}")
    if 'Version' in detail:
        output.append(f"Version: {detail['Version']}")
    if 'Description' in detail:
        output.append(f"Description: {detail['Description']}")
    if 'Status' in detail:
        status = detail['Status']
        if 'State' in status:
            output.append(f"State: {status['State']}")
        if 'Health' in status:
            output.append(f"Health: {status['Health']}")
    if 'Updateable' in detail:
        output.append(f"Updateable: {detail['Updateable']}")
    if 'Manufacturer' in detail:
        output.append(f"Manufacturer: {detail['Manufacturer']}")
    if 'ReleaseDate' in detail:
        output.append(f"Release Date: {detail['ReleaseDate']}")
    if 'SoftwareId' in detail:
        output.append(f"Software ID: {detail['SoftwareId']}")
    
    return '\n'.join(output)


def get_firmware_detail(bmc, component, debug=False, json_output=False):
    """Display detailed information for a specific firmware component"""
    ret, detail = bmc.get_firmware_component_info(component)
    if ret == 0:
        if json_output or debug:
            print(json.dumps(detail, indent=2))
        else:
            print(format_firmware_detail(detail))
    else:
        if json_output:
            error_data = {"error": f"Failed to get firmware component details", "code": ret}
            print(json.dumps(error_data, indent=2))
        else:
            print(f"Failed to get firmware component details: Error code {ret}")
        return 1
    return 0


def perform_reset(bmc, reset_type):
    """Perform system reset"""
    print(f"\nPerforming {reset_type}...")
    
    if reset_type == 'cpu-reset':
        ret = bmc.request_cpu_reset()
    elif reset_type == 'power-cycle':
        ret = bmc.request_power_cycle(immediate=False)
    elif reset_type == 'power-cycle-immediate':
        ret = bmc.request_power_cycle(immediate=True)
    elif reset_type == 'power-cycle-bypass':
        ret = bmc.request_power_cycle_bypass()
    else:
        print(f"Unknown reset type: {reset_type}")
        return 1
    
    # Handle both tuple and integer returns
    if isinstance(ret, tuple):
        ret_code = ret[0] if ret else 1
    else:
        ret_code = ret
    
    if ret_code == 0:
        print(f"{reset_type} initiated successfully")
        print("System will reset shortly...")
        # Give some time for the message to be displayed
        time.sleep(2)
    else:
        print(f"{reset_type} failed with error code: {ret_code}")
        return 1
    
    return 0


def update_firmware(bmc, fw_path, force):
    """Update firmware with progress tracking"""
    if not os.path.exists(fw_path):
        print(f"Firmware file not found: {fw_path}")
        return 1
        
    print(f"\nUpdating firmware with: {fw_path}")
    if force:
        print("Force update enabled")
    
    # Progress callback function
    progress_bar_shown = False
    last_percent = 0
    
    def progress_callback(progress_data):
        nonlocal progress_bar_shown, last_percent
        percent = progress_data.get('percent', 0)
        
        # Show progress bar on first update
        if not progress_bar_shown:
            print("\nProgress:")
            progress_bar_shown = True
        
        # Update progress bar
        if percent > last_percent:
            bar_length = 50
            filled_length = int(bar_length * percent // 100)
            bar = '█' * filled_length + '-' * (bar_length - filled_length)
            print(f'\r|{bar}| {percent}% Complete', end='', flush=True)
            last_percent = percent
    
    # Perform update with progress callback
    ret = bmc.update_firmware(fw_path, force_update=force, progress_callback=progress_callback)
    
    # Ensure we're on a new line after progress bar
    if progress_bar_shown:
        print()
    
    if ret == 0:
        print("\nFirmware update completed successfully")
    else:
        print(f"\nFirmware update failed with error code: {ret}")
        return 1
    
    return 0


def follow_thermal_link(bmc, uri):
    """
    Follow a thermal subsystem link to get actual sensor data
    This is a helper for newer Redfish implementations that use links
    """
    try:
        import json
        
        # Access the redfish client from the BMC object
        rf_client = getattr(bmc, 'rf_client', None)
        if not rf_client:
            return (1, f"Cannot follow link {uri} - rf_client not available")
        
        # Build the GET command using the private method (same pattern as in bmc.py)
        try:
            cmd = rf_client._RedfishClient__build_get_cmd(uri)
            cmd_result, http_status, response, error = rf_client.exec_curl_cmd(cmd)
            
            # Import RedfishClient to check error codes
            from cumulus.bmc_redfish.redfish_client import RedfishClient
            
            if cmd_result == RedfishClient.ERR_CODE_OK:
                # Parse the JSON response
                try:
                    data = json.loads(response)
                    # Return success with the data
                    return (0, data)
                except json.JSONDecodeError as e:
                    # Return success with empty data for empty responses
                    if not response or response.strip() == "":
                        return (0, {})
                    return (1, f"Failed to parse JSON from {uri}: {str(e)}")
            else:
                # Check if response contains valid data despite error code
                if response:
                    try:
                        data = json.loads(response)
                        # If we got valid JSON with expected structure, treat as success
                        # This handles endpoints that return data with non-200 status
                        if isinstance(data, dict):
                            # Any valid JSON dict response is considered success
                            # Even empty Members arrays are valid responses
                            return (0, data)
                    except:
                        pass
                
                # Handle special cases
                if http_status == '404':
                    # 404 is not really an error for optional endpoints
                    return (0, {"error": "Not Found", "status": 404})
                elif http_status == '501':
                    # 501 Not Implemented - feature not available
                    return (0, {"error": "Not Implemented", "status": 501})
                
                # For any other error, still return success but with error info
                # This prevents false error reporting
                return (0, {"error": f"HTTP {http_status}", "details": error if error else 'Unknown'})
                
        except AttributeError:
            # Fallback: rf_client doesn't have the expected methods
            # Return success with empty data rather than error
            return (0, {"error": "Method not available"})
            
    except Exception as e:
        # Even exceptions should not be treated as collection errors
        # Return success with error details
            return (0, {"error": f"Exception: {str(e)}"})


def rf_client_get(bmc, uri):
    """
    Generic GET method for rf_client to fetch any Redfish URI
    This implements the missing get() method functionality
    """
    try:
        # Access the redfish client from the BMC object
        rf_client = getattr(bmc, 'rf_client', None)
        if not rf_client:
            return (1, None)
        
        # Build the GET command using the private method
        try:
            from cumulus.bmc_redfish.redfish_client import RedfishClient
            cmd = rf_client._RedfishClient__build_get_cmd(uri)
            cmd_result, http_status, response, error = rf_client.exec_curl_cmd(cmd)
            
            if cmd_result == RedfishClient.ERR_CODE_OK:
                # Parse the JSON response
                try:
                    import json
                    data = json.loads(response)
                    return (0, data)
                except json.JSONDecodeError:
                    # Empty response or non-JSON
                    if not response or response.strip() == "":
                        return (0, {})
                    return (1, None)
            else:
                # Return error but don't fail collection
                return (0, {"error": f"HTTP {http_status}: {error}"})
                
        except AttributeError:
            # Methods not available
            return (1, None)
            
    except Exception as e:
        return (0, {"error": str(e)})


def collect_all_bmc_data(bmc, timeout=300, max_workers=10):
    """
    Collect ALL available BMC data in parallel
    Returns: (success_count, total_count, all_data)
    """
    all_data = {
        "collection_time": datetime.now().isoformat(),
        "data": {},
        "errors": []
    }
    
    # Define all collection tasks
    collection_tasks = [
        ("system_info", lambda: bmc.get_system_info()),
        ("thermal_info", lambda: bmc.get_thermal_info()),
        ("power_info", lambda: bmc.get_power_info()),
        ("firmware_inventory", lambda: bmc.get_firmware_inventory()),
    ]
    
    # Add detailed firmware component info
    try:
        ret, inventory = bmc.get_firmware_inventory()
        if ret == 0 and inventory:
            for fw in inventory[:5]:  # Limit to first 5 to avoid too many requests
                if "@odata.id" in fw:
                    component = fw["@odata.id"].split("/")[-1]
                    collection_tasks.append(
                        (f"firmware_detail_{component}", 
                         lambda c=component: bmc.get_firmware_component_info(c))
                    )
    except Exception:  # pylint: disable=broad-except
        pass
    
    # Check if we need to follow thermal subsystem links
    try:
        ret, thermal_info = bmc.get_thermal_info()
        if ret == 0 and isinstance(thermal_info, dict):
            # Check if this is the newer ThermalSubsystem format
            if "@odata.type" in thermal_info and "ThermalSubsystem" in thermal_info["@odata.type"]:
                # Add tasks to get the actual sensor data
                if "Fans" in thermal_info and isinstance(thermal_info["Fans"], dict):
                    fans_uri = thermal_info["Fans"].get("@odata.id")
                    if fans_uri:
                        collection_tasks.append(("thermal_fans", lambda: follow_thermal_link(bmc, fans_uri)))
                
                if "ThermalMetrics" in thermal_info and isinstance(thermal_info["ThermalMetrics"], dict):
                    metrics_uri = thermal_info["ThermalMetrics"].get("@odata.id")
                    if metrics_uri:
                        collection_tasks.append(("thermal_metrics", lambda: follow_thermal_link(bmc, metrics_uri)))
                
                # Check for liquid cooling / leak detection
                if "LeakDetection" in thermal_info and isinstance(thermal_info["LeakDetection"], dict):
                    leak_uri = thermal_info["LeakDetection"].get("@odata.id")
                    if leak_uri:
                        collection_tasks.append(("liquid_cooling_leak_detection", lambda: follow_thermal_link(bmc, leak_uri)))
                
                # Check for coolant information (for liquid cooling systems)
                if "Coolant" in thermal_info and isinstance(thermal_info["Coolant"], dict):
                    coolant_uri = thermal_info["Coolant"].get("@odata.id")
                    if coolant_uri:
                        collection_tasks.append(("liquid_cooling_coolant", lambda: follow_thermal_link(bmc, coolant_uri)))
                
                # Check for coolant connectors
                if "CoolantConnectors" in thermal_info and isinstance(thermal_info["CoolantConnectors"], dict):
                    connectors_uri = thermal_info["CoolantConnectors"].get("@odata.id")
                    if connectors_uri:
                        collection_tasks.append(("liquid_cooling_connectors", lambda: follow_thermal_link(bmc, connectors_uri)))
    except Exception:  # pylint: disable=broad-except
        pass
    
    # Try to collect additional data through direct API calls if available
    # This provides comprehensive data collection beyond the basic API
    # Note: rf_client might not be exposed in all BMC implementations
    # Use our custom rf_client_get function instead of the missing get() method
    try:
        rf_client = getattr(bmc, 'rf_client', None)
        if rf_client and hasattr(rf_client, 'exec_curl_cmd'):
            extended_endpoints = [
            # Event and log services
            ("event_logs", "/redfish/v1/Systems/System_0/LogServices/EventLog/Entries"),
            ("bmc_logs", "/redfish/v1/Managers/BMC_0/LogServices/Log/Entries"),
            ("audit_logs", "/redfish/v1/Managers/BMC_0/LogServices/AuditLog/Entries"),
            
            # System and chassis information
            ("chassis_collection", "/redfish/v1/Chassis"),
            ("managers_collection", "/redfish/v1/Managers"),
            ("systems_collection", "/redfish/v1/Systems"),
            
            # Detailed subsystems
            ("thermal_subsystem", "/redfish/v1/Chassis/System_0/ThermalSubsystem"),
            ("power_subsystem", "/redfish/v1/Chassis/System_0/PowerSubsystem"),
            ("memory_collection", "/redfish/v1/Systems/System_0/Memory"),
            ("processor_collection", "/redfish/v1/Systems/System_0/Processors"),
            ("storage_collection", "/redfish/v1/Systems/System_0/Storage"),
            ("network_interfaces", "/redfish/v1/Systems/System_0/NetworkInterfaces"),
            
            # BMC specific information
            ("bmc_network_protocol", "/redfish/v1/Managers/BMC_0/NetworkProtocol"),
            ("bmc_ethernet_interfaces", "/redfish/v1/Managers/BMC_0/EthernetInterfaces"),
            
            # Service information
            ("account_service", "/redfish/v1/AccountService"),
            ("session_service", "/redfish/v1/SessionService"),
            ("event_service", "/redfish/v1/EventService"),
            ("update_service", "/redfish/v1/UpdateService"),
            ("task_service", "/redfish/v1/TaskService"),
            ("certificate_service", "/redfish/v1/CertificateService"),
            ("telemetry_service", "/redfish/v1/TelemetryService"),
            
            # Registries and schemas
            ("registries", "/redfish/v1/Registries"),
            ("json_schemas", "/redfish/v1/JsonSchemas"),
        ]
        
            # Add alternate URIs for different BMC implementations
            alternate_event_uris = [
                ("event_logs_alt1", "/redfish/v1/Managers/System_0/LogServices/EventLog/Entries"),
                ("event_logs_alt2", "/redfish/v1/Systems/1/LogServices/EventLog/Entries"),
                ("sel_logs", "/redfish/v1/Managers/BMC_0/LogServices/SEL/Entries"),
                ("journal_logs", "/redfish/v1/Managers/BMC_0/LogServices/Journal/Entries"),
            ]
            
            # For Juliet BMC specific endpoints (if applicable)
            juliet_specific = [
                ("debug_logs", "/redfish/v1/Managers/BMC_0/LogServices/DebugLog/Entries"),
                ("crash_dumps", "/redfish/v1/Managers/BMC_0/LogServices/Crashdump/Entries"),
                ("redfish_events", "/redfish/v1/EventService/Events"),
                ("metrics", "/redfish/v1/TelemetryService/MetricReports"),
            ]
            
            all_extended_endpoints = extended_endpoints + alternate_event_uris + juliet_specific
            
            for task_name, endpoint in all_extended_endpoints:
                collection_tasks.append(
                    (task_name, lambda ep=endpoint: rf_client_get(bmc, ep))
                )
                
            # Also try to get historical data with query parameters
            historical_queries = [
                ("event_logs_full", "/redfish/v1/Systems/System_0/LogServices/EventLog/Entries?$top=1000"),
                ("event_logs_critical", "/redfish/v1/Systems/System_0/LogServices/EventLog/Entries?$filter=Severity eq 'Critical'"),
                ("event_logs_recent", "/redfish/v1/Systems/System_0/LogServices/EventLog/Entries?$orderby=Created desc&$top=100"),
            ]
            
            for task_name, endpoint in historical_queries:
                collection_tasks.append(
                    (task_name, lambda ep=endpoint: rf_client_get(bmc, ep))
                )
    except Exception as e:
        # If we can't access rf_client, continue with basic collection
        print(f"Note: Extended data collection not available: {e}", file=sys.stderr)
        pass
    
    success_count = 0
    
    # Execute all tasks in parallel
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # Submit all tasks
        future_to_task = {}
        for task_name, task_func in collection_tasks:
            future = executor.submit(task_func)
            future_to_task[future] = task_name
        
        # Collect results as they complete
        for future in concurrent.futures.as_completed(future_to_task, timeout=timeout):
            task_name = future_to_task[future]
            try:
                result = future.result()
                
                # Handle different result types
                if result is None:
                    all_data["errors"].append({
                        "task": task_name,
                        "error": "No data returned"
                    })
                elif isinstance(result, tuple) and len(result) == 2:
                    ret, data = result
                    if ret == 0 and data is not None:
                        all_data["data"][task_name] = data
                        success_count += 1
                    else:
                        all_data["errors"].append({
                            "task": task_name,
                            "error": f"Return code: {ret}",
                            "data": str(data) if isinstance(data, str) else data
                        })
                elif isinstance(result, dict):
                    # Direct dictionary result
                    all_data["data"][task_name] = result
                    success_count += 1
                elif isinstance(result, str):
                    # String result - likely an error
                    all_data["errors"].append({
                        "task": task_name,
                        "error": f"Unexpected string result: {result}"
                    })
                else:
                    # Other types - store as is
                    all_data["data"][task_name] = result
                    success_count += 1
                    
            except Exception as e:
                all_data["errors"].append({
                    "task": task_name,
                    "error": f"Exception: {type(e).__name__}: {str(e)}"
                })
    
    return success_count, len(collection_tasks), all_data


def generate_health_summary(all_data):
    """Generate a health summary from collected data"""
    summary = {
        "system_health": "Unknown",
        "critical_issues": [],
        "warnings": [],
        "statistics": {
            "total_sensors": 0,
            "abnormal_sensors": 0,
            "total_events": 0,
            "critical_events": 0,
            "error_events": 0,
            "firmware_components": 0
        }
    }
    
    # Check system health
    data_section = all_data.get("data", {})
    if "system_info" in data_section:
        sys_info = data_section["system_info"]
        if isinstance(sys_info, dict):
            status = sys_info.get("Status", {})
            if isinstance(status, dict):
                summary["system_health"] = status.get("Health", "Unknown")
                if status.get("Health") not in ["OK", "Optimal", None]:
                    summary["critical_issues"].append({
                        "type": "system_health",
                        "message": f"System health is {status.get('Health')}",
                        "details": status
                    })
        elif isinstance(sys_info, str):
            # Handle case where system_info might be an error string
            summary["warnings"].append({
                "type": "data_format",
                "message": "System info returned as string instead of dict"
            })
    
    # Count events and check for critical ones
    for log_type in ["event_logs", "bmc_logs", "audit_logs"]:
        if log_type in data_section:
            log_data = data_section[log_type]
            if isinstance(log_data, dict) and "Members" in log_data:
                events = log_data.get("Members", [])
                if isinstance(events, list):
                    summary["statistics"]["total_events"] += len(events)
                    
                    for event in events:
                        if isinstance(event, dict):
                            severity = event.get("Severity", "").lower()
                            if severity in ["critical", "fatal"]:
                                summary["statistics"]["critical_events"] += 1
                                summary["critical_issues"].append({
                                    "type": "event",
                                    "message": event.get("Message", "Unknown critical event"),
                                    "timestamp": event.get("Created"),
                                    "id": event.get("Id")
                                })
                            elif severity in ["error"]:
                                summary["statistics"]["error_events"] += 1
                                summary["warnings"].append({
                                    "type": "event",
                                    "message": event.get("Message", "Unknown error event"),
                                    "timestamp": event.get("Created")
                                })
    
    # Check thermal sensors - handle both old and new formats
    if "thermal_info" in data_section:
        thermal_data = data_section["thermal_info"]
        if isinstance(thermal_data, dict):
            # Check if this is the old format with inline data
            temperatures = thermal_data.get("Temperatures", [])
            if isinstance(temperatures, list):
                for temp in temperatures:
                    if isinstance(temp, dict):
                        summary["statistics"]["total_sensors"] += 1
                        temp_status = temp.get("Status", {})
                        if isinstance(temp_status, dict):
                            health = temp_status.get("Health")
                            if health not in ["OK", "Normal", None]:
                                summary["statistics"]["abnormal_sensors"] += 1
                                summary["warnings"].append({
                                    "type": "sensor",
                                    "sensor_type": "temperature",
                                    "name": temp.get("Name", "Unknown"),
                                    "status": health,
                                    "reading": temp.get("ReadingCelsius")
                                })
            
            # Check fans in old format
            fans = thermal_data.get("Fans", [])
            if isinstance(fans, list):
                for fan in fans:
                    if isinstance(fan, dict):
                        summary["statistics"]["total_sensors"] += 1
                        fan_status = fan.get("Status", {})
                        if isinstance(fan_status, dict):
                            health = fan_status.get("Health")
                            if health not in ["OK", "Normal", None]:
                                summary["statistics"]["abnormal_sensors"] += 1
                                summary["warnings"].append({
                                    "type": "sensor",
                                    "sensor_type": "fan",
                                    "name": fan.get("Name", "Unknown"),
                                    "status": health,
                                    "reading": fan.get("Reading")
                                })
    
    # Check for new format thermal data in separate endpoints
    if "thermal_fans" in data_section:
        fans_data = data_section["thermal_fans"]
        if isinstance(fans_data, dict) and "Members" in fans_data:
            members = fans_data.get("Members", [])
            if isinstance(members, list):
                summary["statistics"]["total_sensors"] += len(members)
                # You could add more detailed processing here if needed
    
    if "thermal_metrics" in data_section:
        metrics_data = data_section["thermal_metrics"]
        if isinstance(metrics_data, dict):
            # Process temperature readings from metrics
            temp_readings = metrics_data.get("TemperatureReadingsCelsius", [])
            if isinstance(temp_readings, list):
                summary["statistics"]["total_sensors"] += len(temp_readings)
    
    # Check liquid cooling / leak detection status
    if "liquid_cooling_leak_detection" in data_section:
        leak_data = data_section["liquid_cooling_leak_detection"]
        if isinstance(leak_data, dict):
            # Add liquid cooling statistics
            if "statistics" not in summary:
                summary["statistics"] = {}
            summary["statistics"]["liquid_cooling_present"] = True
            
            # Check for leak detection sensors
            leak_detectors = leak_data.get("LeakDetectors", [])
            if isinstance(leak_detectors, list):
                summary["statistics"]["leak_detectors"] = len(leak_detectors)
                for detector in leak_detectors:
                    if isinstance(detector, dict):
                        detector_status = detector.get("DetectorState")
                        if detector_status and detector_status != "OK":
                            summary["critical_issues"].append({
                                "type": "liquid_cooling",
                                "severity": "critical",
                                "message": f"Leak detected at {detector.get('Name', 'Unknown location')}",
                                "detector_state": detector_status,
                                "location": detector.get("PhysicalContext", "Unknown")
                            })
            
            # Check overall leak detection status
            leak_status = leak_data.get("Status", {})
            if isinstance(leak_status, dict):
                health = leak_status.get("Health")
                if health and health not in ["OK", "Normal", None]:
                    summary["warnings"].append({
                        "type": "liquid_cooling", 
                        "message": f"Liquid cooling system health: {health}",
                        "status": leak_status
                    })
    
    # Check coolant loop data
    if "liquid_cooling_coolant" in data_section:
        coolant_data = data_section["liquid_cooling_coolant"]
        if isinstance(coolant_data, dict):
            # Process coolant loops
            if "Members" in coolant_data and isinstance(coolant_data["Members"], list):
                for coolant in coolant_data["Members"]:
                    if isinstance(coolant, dict):
                        # Check coolant temperature
                        coolant_temp = coolant.get("CoolantLevelPercent", {})
                        if isinstance(coolant_temp, dict):
                            reading = coolant_temp.get("Reading")
                            if reading is not None and reading < 20:  # Low coolant level
                                summary["warnings"].append({
                                    "type": "liquid_cooling",
                                    "message": f"Low coolant level: {reading}%",
                                    "coolant_loop": coolant.get("Name", "Unknown")
                                })
                        
                        # Check flow rate if available
                        flow_rate = coolant.get("FlowLitersPerMinute", {})
                        if isinstance(flow_rate, dict):
                            flow_reading = flow_rate.get("Reading")
                            if flow_reading is not None and flow_reading < 1.0:  # Low flow
                                summary["warnings"].append({
                                    "type": "liquid_cooling",
                                    "message": f"Low coolant flow rate: {flow_reading} L/min",
                                    "coolant_loop": coolant.get("Name", "Unknown")
                                })
    
    # Count firmware components
    if "firmware_inventory" in data_section:
        fw_data = data_section["firmware_inventory"]
        if isinstance(fw_data, list):
            summary["statistics"]["firmware_components"] = len(fw_data)
    
    return summary


def recursive_redfish_get(bmc, uri, visited=None, max_depth=10, current_depth=0):
    """
    Recursively follow Redfish links and collect all data
    Returns a dictionary with the URI as key and data/links as value
    """
    if visited is None:
        visited = set()
    
    if current_depth >= max_depth:
        return {"_max_depth_reached": True, "_uri": uri}
    
    if uri in visited:
        return {"_already_visited": True, "_uri": uri}
    
    visited.add(uri)
    
    try:
        # Always use direct HTTP requests since rf_client doesn't have get() method
        if hasattr(bmc, 'host') and hasattr(bmc, 'username') and hasattr(bmc, 'password'):
            try:
                import requests
                from requests.auth import HTTPBasicAuth
                
                # Construct full URL
                base_url = f"https://{bmc.host}"
                full_url = base_url + uri
                
                # Make request with basic auth
                response = requests.get(
                    full_url, 
                    auth=HTTPBasicAuth(bmc.username, bmc.password),
                    verify=False,  # Skip SSL verification like curl -k
                    timeout=10
                )
                
                if response.status_code == 200:
                    data = response.json()
                else:
                    return {"_error": f"HTTP {response.status_code}", "_uri": uri}
            except ImportError:
                return {"_error": "requests module not available - install with: pip install requests", "_uri": uri}
        else:
            return {"_error": "No credentials available", "_uri": uri}
        
        result = {"_uri": uri, "_data": data}
        
        # Look for @odata.id links to follow
        if isinstance(data, dict):
            for key, value in data.items():
                if isinstance(value, dict) and "@odata.id" in value:
                    # This is a link to follow
                    link_uri = value["@odata.id"]
                    result[f"_link_{key}"] = recursive_redfish_get(
                        bmc, link_uri, visited, max_depth, current_depth + 1
                    )
                elif key == "Members" and isinstance(value, list):
                    # This is a collection with members
                    result["_members"] = []
                    for member in value:
                        if isinstance(member, dict) and "@odata.id" in member:
                            member_uri = member["@odata.id"]
                            member_data = recursive_redfish_get(
                                bmc, member_uri, visited, max_depth, current_depth + 1
                            )
                            result["_members"].append(member_data)
        
        return result
        
    except Exception as e:
        return {"_error": str(e), "_uri": uri}


def get_sensor_dump(bmc, output_file=None, start_uri=None, max_depth=10):
    """
    Dump all sensor data by recursively following Redfish links
    """
    print("Collecting sensor data...", file=sys.stderr)
    
    # Default starting points for sensor data
    default_uris = [
        "/redfish/v1/Chassis/MGX_BMC_0/ThermalSubsystem",
        "/redfish/v1/Chassis/MGX_BMC_0/PowerSubsystem",
        "/redfish/v1/Chassis/MGX_BMC_0/Sensors",
        "/redfish/v1/Systems/System_0/Storage"
    ]
    
    if start_uri:
        uris_to_explore = [start_uri]
    else:
        uris_to_explore = default_uris
    
    all_data = {
        "collection_time": datetime.now().isoformat(),
        "sensor_data": {}
    }
    
    # Try to import requests for direct HTTP access
    try:
        import requests
        try:
            requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
        except AttributeError:
            pass  # urllib3 might not be available
    except ImportError:
        print("Warning: 'requests' module not available. Some features may be limited.", file=sys.stderr)
        print("Install with: pip install requests", file=sys.stderr)
    
    # Collect data from each starting URI
    for uri in uris_to_explore:
        print(f"Exploring {uri}...", file=sys.stderr)
        data = recursive_redfish_get(bmc, uri, max_depth=max_depth)
        all_data["sensor_data"][uri] = data
    
    # Generate summary
    summary = {
        "total_resources": 0,
        "total_sensors": 0,
        "sensor_types": {},
        "health_summary": {}
    }
    
    def count_resources(data, summary):
        if isinstance(data, dict):
            if "_data" in data:
                summary["total_resources"] += 1
                # Check for sensor-specific fields
                if "DetectorState" in data.get("_data", {}):
                    summary["total_sensors"] += 1
                    sensor_type = data["_data"].get("LeakDetectorType", "Unknown")
                    summary["sensor_types"][sensor_type] = summary["sensor_types"].get(sensor_type, 0) + 1
                    
                    # Check health
                    health = data["_data"].get("Status", {}).get("Health", "Unknown")
                    summary["health_summary"][health] = summary["health_summary"].get(health, 0) + 1
                
                # Check for temperature readings
                if "ReadingCelsius" in data.get("_data", {}):
                    summary["total_sensors"] += 1
                    summary["sensor_types"]["Temperature"] = summary["sensor_types"].get("Temperature", 0) + 1
                
                # Check for fan readings
                if "Reading" in data.get("_data", {}) and "SpeedPercent" in str(data.get("_data", {})):
                    summary["total_sensors"] += 1
                    summary["sensor_types"]["Fan"] = summary["sensor_types"].get("Fan", 0) + 1
            
            # Recurse through the structure
            for key, value in data.items():
                if key != "_data" and isinstance(value, (dict, list)):
                    if isinstance(value, list):
                        for item in value:
                            count_resources(item, summary)
                    else:
                        count_resources(value, summary)
    
    count_resources(all_data["sensor_data"], summary)
    all_data["summary"] = summary
    
    # Output results
    if output_file:
        output_file = os.path.expanduser(output_file)
        print(f"Writing sensor data to {output_file}...", file=sys.stderr)
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(all_data, f, indent=2, default=str)
        
        file_size_mb = os.path.getsize(output_file) / (1024 * 1024)
        print(f"Sensor dump complete: {output_file} ({file_size_mb:.2f} MB)", file=sys.stderr)
    else:
        # Print to stdout
        print(json.dumps(all_data, indent=2, default=str))
    
    # Print summary
    print("\n=== Sensor Dump Summary ===", file=sys.stderr)
    print(f"Total Resources: {summary['total_resources']}", file=sys.stderr)
    print(f"Total Sensors: {summary['total_sensors']}", file=sys.stderr)
    if summary['sensor_types']:
        print("\nSensor Types:", file=sys.stderr)
        for sensor_type, count in summary['sensor_types'].items():
            print(f"  {sensor_type}: {count}", file=sys.stderr)
    if summary['health_summary']:
        print("\nHealth Summary:", file=sys.stderr)
        for health, count in summary['health_summary'].items():
            print(f"  {health}: {count}", file=sys.stderr)
    
    return 0


def reset_password(bmc, user, new_password, debug=False, json_output=False):
    """Reset BMC user password"""
    try:
        ret = bmc.login()
        if ret != 0:
            return 1
        
        # Change the password using Redfish API
        # Use the correct RedfishClient method with proper parameter order
        # redfish_api_change_login_password expects (new_password, user) not (user, new_password)
        if hasattr(bmc, 'rf_client') and hasattr(bmc.rf_client, 'redfish_api_change_login_password'):
            ret, msg = bmc.rf_client.redfish_api_change_login_password(new_password, user)
        else:
            ret = 1
            msg = 'Password reset method not available in BMC client'
        
        if ret == 0:
            if json_output:
                output = {
                    "status": "success",
                    "message": f"Password reset successfully for user {user}",
                    "user": user
                }
                print(json.dumps(output, indent=2))
            else:
                print(f"Successfully reset password for user: {user}")
            return 0
        else:
            if json_output:
                output = {
                    "status": "failed",
                    "message": msg or f"Failed to reset password for user {user}",
                    "user": user
                }
                print(json.dumps(output, indent=2))
            else:
                print(f"Failed to reset password for user {user}: {msg}")
            return 1
            
    except Exception as e:
        if json_output:
            output = {
                "status": "failed",
                "message": str(e),
                "user": user
            }
            print(json.dumps(output, indent=2))
        else:
            print(f"Failed to reset password: {e}")
        return 1


def get_collect_all(bmc, output_file=None, timeout=300, json_output=True):  # pylint: disable=unused-argument
    """Collect all BMC data and optionally save to file"""
    print("Collecting ALL BMC data...", file=sys.stderr)
    print(f"This may take up to {timeout} seconds", file=sys.stderr)
    
    start_time = time.time()
    
    try:
        success_count, total_count, all_data = collect_all_bmc_data(bmc, timeout=timeout)
    except Exception as e:
        print(f"Error during collection: {type(e).__name__}: {str(e)}", file=sys.stderr)
        # Return minimal error data
        all_data = {
            "collection_time": datetime.now().isoformat(),
            "data": {},
            "errors": [{"task": "collection", "error": str(e)}]
        }
        success_count = 0
        total_count = 1
    
    duration = time.time() - start_time
    
    # Generate comprehensive health summary
    try:
        health_summary = generate_health_summary(all_data)
    except Exception as e:
        print(f"Warning: Could not generate health summary: {e}", file=sys.stderr)
        health_summary = {
            "system_health": "Unknown",
            "critical_issues": [],
            "warnings": [],
            "statistics": {}
        }
    
    # Add summary
    all_data["summary"] = {
        "collection_duration": duration,
        "total_tasks": total_count,
        "successful_tasks": success_count,
        "failed_tasks": total_count - success_count,
        "health_summary": health_summary
    }
    
    # Output data
    if output_file:
        try:
            with open(output_file, 'w', encoding='utf-8') as f:
                json.dump(all_data, f, indent=2, default=str, sort_keys=True)
            file_size_mb = os.path.getsize(output_file) / (1024 * 1024)
            
            # Print collection summary to stderr
            print("\n=== Collection Complete ===", file=sys.stderr)
            print(f"Duration: {duration:.1f} seconds", file=sys.stderr)
            print(f"Output file: {output_file} ({file_size_mb:.1f} MB)", file=sys.stderr)
            print(f"Tasks: {success_count}/{total_count} successful", file=sys.stderr)
            
            # Check if we have the newer ThermalSubsystem format
            if "thermal_info" in all_data.get("data", {}):
                thermal_info = all_data["data"]["thermal_info"]
                if isinstance(thermal_info, dict) and "@odata.type" in thermal_info:
                    if "ThermalSubsystem" in thermal_info["@odata.type"]:
                        print("\nNote: Your BMC uses the newer ThermalSubsystem format.", file=sys.stderr)
                        print("Detailed sensor data is in linked resources. The rf_client API", file=sys.stderr)
                        print("would be needed to follow these links for complete sensor data.", file=sys.stderr)
            
            # Print health summary
            print("\n=== System Health Summary ===", file=sys.stderr)
            print(f"Overall Health: {health_summary.get('system_health', 'Unknown')}", file=sys.stderr)
            stats = health_summary.get('statistics', {})
            print(f"Total Sensors: {stats.get('total_sensors', 0)}", file=sys.stderr)
            print(f"Abnormal Sensors: {stats.get('abnormal_sensors', 0)}", file=sys.stderr)
            print(f"Total Events: {stats.get('total_events', 0)}", file=sys.stderr)
            print(f"Critical Events: {stats.get('critical_events', 0)}", file=sys.stderr)
            print(f"Error Events: {stats.get('error_events', 0)}", file=sys.stderr)
            
            # Display liquid cooling info if present
            if stats.get('liquid_cooling_present', False):
                print("\n=== Liquid Cooling System ===", file=sys.stderr)
                print(f"Leak Detectors: {stats.get('leak_detectors', 0)}", file=sys.stderr)
            
            critical_issues = health_summary.get("critical_issues", [])
            if critical_issues:
                print(f"\n=== Critical Issues ({len(critical_issues)}) ===", file=sys.stderr)
                for issue in critical_issues[:5]:
                    print(f"- {issue.get('message', 'Unknown issue')}", file=sys.stderr)
                if len(critical_issues) > 5:
                    print(f"... and {len(critical_issues) - 5} more", file=sys.stderr)
            
            errors = all_data.get("errors", [])
            if errors:
                print(f"\n=== Collection Errors ({len(errors)}) ===", file=sys.stderr)
                for err in errors[:5]:
                    if isinstance(err, dict):
                        print(f"- {err.get('task', 'Unknown')}: {err.get('error', 'Unknown error')}", file=sys.stderr)
                if len(errors) > 5:
                    print(f"... and {len(errors) - 5} more", file=sys.stderr)
            
            return 0
        except Exception as e:
            print(f"Failed to write output file: {e}", file=sys.stderr)
            return 1
    else:
        # Print to stdout
        print(json.dumps(all_data, indent=2, default=str))
        return 0


def main():
    # Get defaults before creating parser
    defaults = get_default_values()
    
    parser = argparse.ArgumentParser(
        description='BMC Redfish Client - Interact with BMC via Redfish API',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
Examples:
  # Get system information (with explicit credentials)
  %(prog)s --host 10.7.114.123 --username root --password ABYX12#14artb info
  
  # Get thermal information (using config file credentials)
  %(prog)s thermal
  
  # Get firmware inventory
  %(prog)s inventory
  
  # Get detailed info for a specific firmware component
  %(prog)s firmware-detail CPLD_0
  
  # Perform CPU reset
  %(prog)s reset cpu-reset
  
  # Update firmware
  %(prog)s firmware --file /path/to/firmware.bin
  
  # Collect ALL BMC data
  %(prog)s collect-all --output bmc_dump.json
  
  # Recursively dump all sensor data (including liquid cooling)
  %(prog)s sensor-dump --output sensors.json
  
  # Dump specific subsystem
  %(prog)s sensor-dump --uri /redfish/v1/Chassis/MGX_BMC_0/ThermalSubsystem/LeakDetection
  
  # Reset BMC password
  %(prog)s password-reset --user admin --new-password NewSecurePass123!
  
Configuration:
  You can set default values via:
  1. Config file: /etc/cumulus/bmc.conf or ~/.bmc.conf
  2. Environment variables: BMC_HOST, BMC_USER, BMC_PASS
  
  Config file format:
    BMC_HOST=10.7.114.123
    BMC_USER=root
    BMC_PASS=yourpassword
        '''
    )
    
    # Connection arguments
    parser.add_argument('--host', default=defaults['host'], 
                       help='BMC IP address' + (f" (default from config/env: {defaults['host']})" if defaults['host'] else " (required)"))
    parser.add_argument('--username', default=defaults['username'], 
                       help=f"BMC username (default: {defaults['username']})")
    parser.add_argument('--password', default=defaults['password'], 
                       help='BMC password' + (f" (default from config/env: ***)" if defaults['password'] else " (required)"))
    parser.add_argument('--debug', action='store_true', help='Enable debug logging')
    parser.add_argument('-j', '--json', action='store_true', help='Generate JSON output')
    
    # Commands
    subparsers = parser.add_subparsers(dest='command', help='Command to execute')
    
    # Info command
    subparsers.add_parser('info', help='Get system information')
    
    # Thermal command
    subparsers.add_parser('thermal', help='Get thermal information')
    
    # Power command
    subparsers.add_parser('power', help='Get power information')
    
    # Firmware inventory command
    subparsers.add_parser('inventory', help='Get firmware inventory')
    
    # Firmware detail command
    fw_detail_parser = subparsers.add_parser('firmware-detail', help='Get detailed info for a firmware component')
    fw_detail_parser.add_argument('component', help='Firmware component ID (e.g., CPLD_0, MGX_FW_BMC_0)')
    
    # Reset command
    reset_parser = subparsers.add_parser('reset', help='Perform system reset')
    reset_parser.add_argument('reset_type', 
                             choices=['cpu-reset', 'power-cycle', 'power-cycle-immediate', 'power-cycle-bypass'],
                             help='Type of reset to perform')
    
    # Firmware update command
    fw_parser = subparsers.add_parser('firmware', help='Update firmware')
    fw_parser.add_argument('--file', required=True, help='Path to firmware file')
    fw_parser.add_argument('--force', action='store_true', help='Force update even if version is older')
    
    # Collect-all command
    collect_parser = subparsers.add_parser('collect-all', help='Collect ALL available BMC data')
    collect_parser.add_argument('--output', required=True, help='Output file for comprehensive dump (JSON format)')
    collect_parser.add_argument('--timeout', type=int, default=300, help='Collection timeout in seconds (default: 300)')
    
    # Sensor-dump command
    sensor_parser = subparsers.add_parser('sensor-dump', help='Recursively dump all sensor data')
    sensor_parser.add_argument('--output', required=False, help='Output file (default: stdout)')
    sensor_parser.add_argument('--uri', required=False, help='Starting URI to explore')
    sensor_parser.add_argument('--max-depth', type=int, default=10, help='Maximum depth to follow links (default: 10)')
    
    # Password reset command
    pw_reset_parser = subparsers.add_parser('password-reset', help='Reset BMC user password')
    pw_reset_parser.add_argument('--user', default='admin', help='BMC user to reset (default: admin)')
    pw_reset_parser.add_argument('--new-password', required=True, help='New password to set')
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        return 1
    
    # Validate required arguments
    if not args.host:
        print("Error: BMC host is required. Provide via --host, BMC_HOST environment variable, or config file.")
        print("See --help for configuration options.")
        return 1
    
    if not args.password:
        print("Error: BMC password is required. Provide via --password, BMC_PASS environment variable, or config file.")
        print("See --help for configuration options.")
        return 1
    
    # Get interface from config if available
    interface = defaults.get('interface')
    
    # Create BMC instance
    bmc = BMC(args.host, args.username, args.password, interface=interface)
    # Store credentials for direct HTTP access if needed
    bmc.host = args.host
    bmc.username = args.username
    bmc.password = args.password
    
    if args.debug:
        bmc.enable_debug()
    
    try:
        # Login
        ret = bmc.login()
        if ret != 0:
            print(f"Login failed with error code: {ret}")
            return 1
        
        # Execute command
        if args.command == 'info':
            return get_system_info(bmc, debug=args.debug, json_output=args.json)
        elif args.command == 'thermal':
            return get_thermal_info(bmc, debug=args.debug, json_output=args.json)
        elif args.command == 'power':
            return get_power_info(bmc, debug=args.debug, json_output=args.json)
        elif args.command == 'inventory':
            return get_firmware_inventory(bmc, debug=args.debug, json_output=args.json)
        elif args.command == 'firmware-detail':
            return get_firmware_detail(bmc, args.component, debug=args.debug, json_output=args.json)
        elif args.command == 'reset':
            return perform_reset(bmc, args.reset_type)
        elif args.command == 'firmware':
            return update_firmware(bmc, args.file, args.force)
        elif args.command == 'collect-all':
            return get_collect_all(bmc, args.output, args.timeout, args.json)
        elif args.command == 'sensor-dump':
            return get_sensor_dump(bmc, args.output, args.uri, args.max_depth)
        elif args.command == 'password-reset':
            return reset_password(bmc, args.user, args.new_password, debug=args.debug, json_output=args.json)
        else:
            print(f"Unknown command: {args.command}")
            return 1
            
    except KeyboardInterrupt:
        print("\nOperation cancelled by user")
        return 1
    except Exception as e:
        print(f"Error: {e}")
        return 1
    finally:
        # Always logout
        try:
            bmc.logout()
        except:
            pass


if __name__ == '__main__':
    sys.exit(main())