"""
Risk detection module for RVTools analysis.
Contains individual risk detection functions and a gatherer function.
"""
import re
from typing import Dict, List, Any
import pandas as pd

# Import from our new modules
from .models import RiskLevel, ESXVersionThresholds, PowerStates, GuestStates, NetworkConstants, StorageConstants
from .config import MigrationMethodsConfig
from .helpers import (
    load_sku_data, safe_sheet_access, create_empty_result,
    filter_dataframe_by_condition, get_risk_category, convert_mib_to_tb,
    clean_function_name_for_display
)
from .utils import contains_password_reference, redact_password_content
from .decorators import risk_info

import logging
logger = logging.getLogger(__name__)

########################################################################################################################
#                                                                                                                      #
#                                               Risk Detection Functions                                               #
#                                                                                                                      #
########################################################################################################################

@risk_info(
    level=RiskLevel.INFO,
    description='This shows the distribution of ESX versions found in the uploaded file.',
    alert_message="""Having multiple ESX versions in the environment can lead to compatibility issues and
    increased complexity during migration.<br><br>It's recommended to standardize on a single ESX version
    if possible."""
)
def detect_esx_versions(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect ESX versions and their risk levels."""
    vhost_data = safe_sheet_access(excel_data, 'vHost')
    if vhost_data is None:
        logger.warning("detect_esx_versions: No vHost sheet found")
        return create_empty_result()

    version_counts = vhost_data['ESX Version'].value_counts().to_dict()
    version_risks = {}
    card_risk = "info"

    for version_str in version_counts.keys():
        version_match = re.search(r'ESXi (\d+\.\d+\.\d+)', version_str)
        if version_match:
            version_num = version_match.group(1)
            if version_num < ESXVersionThresholds.ERROR_THRESHOLD:
                version_risks[version_str] = "blocking"
                card_risk = "danger"
            elif version_num < ESXVersionThresholds.WARNING_THRESHOLD:
                version_risks[version_str] = "warning"
                if card_risk != "danger":
                    card_risk = "warning"
            else:
                version_risks[version_str] = "info"

    logger.info(f"detect_esx_versions: Found {len(version_counts)} ESX versions with overall risk level: {card_risk}")
    return {
        'count': len(version_counts),
        'data': version_counts,
        'details': {
            'version_risks': version_risks,
            'card_risk': card_risk,
            'warning_threshold': ESXVersionThresholds.WARNING_THRESHOLD,
            'error_threshold': ESXVersionThresholds.ERROR_THRESHOLD
        }
    }


@risk_info(
    level=RiskLevel.BLOCKING,
    description='vUSB devices are USB devices that are connected to a virtual machine (VM) in a VMware environment.',
    alert_message="""Having vUSB devices connected to VMs can pose a risk during migration, as they cannot be
    transferred to an Azure Managed environment.<br><br>It's recommended to review the list of vUSB devices
    and ensure that they are necessary for the VM's operation before proceeding with the migration."""
)
def detect_vusb_devices(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect USB devices attached to VMs."""
    vusb_data = safe_sheet_access(excel_data, 'vUSB')
    if vusb_data is None:
        logger.warning("detect_vusb_devices: No vUSB sheet found")
        return create_empty_result()

    devices = vusb_data[['VM', 'Powerstate', 'Device Type', 'Connected']].to_dict(orient='records')

    logger.info(f"detect_vusb_devices: Found {len(devices)} USB devices attached to VMs")
    return {
        'count': len(devices),
        'data': devices
    }


@risk_info(
    level='warning',  # Changed to warning as base level since we now have dynamic risk levels
    description='Risky disks are virtual disks that are configured in a way that may pose a risk during migration.',
    alert_message="""This can include disks that are set to "Independent" mode or configured with Raw Device
    Mapping capability:<br>
    <ul>
        <li>Raw Device Mapping in physicalMode: Blocking risk — cannot be migrated</li>
        <li>Raw Device Mapping in virtualMode: Warning — bulk migration possible with disk conversion</li>
        <li>Independent persistent mode: Warning — requires special consideration</li>
    </ul>
    It's recommended to review the list of risky disks and consider reconfiguring them before proceeding with the migration."""
)
def detect_risky_disks(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect disks with migration risks (raw or independent persistent)."""
    if 'vDisk' not in excel_data.sheet_names:
        logger.info("detect_risky_disks: No vDisk sheet found")
        return {'count': 0, 'data': []}

    vdisk_data = excel_data.parse('vDisk')

    # Filter using string comparison directly - no copy needed
    mask = (
        (vdisk_data['Raw'].astype(str).str.lower() == 'true') |
        (vdisk_data['Disk Mode'] == 'independent_persistent')
    )

    # Select only columns that exist in the data
    columns_to_return = ['VM', 'Powerstate', 'Disk', 'Capacity MiB', 'Raw', 'Disk Mode', 'Raw Com. Mode']
    available_columns = [col for col in columns_to_return if col in vdisk_data.columns]

    risky_disks_df = vdisk_data[mask][available_columns].copy()

    # Add dynamic risk level based on Raw Com. Mode
    def determine_risk_level(row):
        # Handle Raw column - convert to string for comparison
        raw_value = str(row.get('Raw', '')).lower()
        if raw_value == 'true':
            raw_com_mode = str(row.get('Raw Com. Mode', '')).lower()
            if raw_com_mode == 'physicalmode':
                return 'blocking'
            elif raw_com_mode == 'virtualmode':
                return 'warning'
            else:
                return 'warning'  # Default for raw disks without specified mode
        elif row.get('Disk Mode') == 'independent_persistent':
            return 'warning'
        else:
            return 'warning'  # Default fallback

    risky_disks_df['Risk Level'] = risky_disks_df.apply(determine_risk_level, axis=1)

    risky_disks = risky_disks_df.to_dict(orient='records')

    # Count blocking vs warning risks
    blocking_count = len([disk for disk in risky_disks if disk.get('Risk Level') == 'blocking'])
    warning_count = len([disk for disk in risky_disks if disk.get('Risk Level') == 'warning'])

    # Determine overall card risk level based on highest severity found
    card_risk = "warning"  # Default since base level is warning
    if blocking_count > 0:
        card_risk = "blocking"

    logger.info(f"detect_risky_disks: Found {len(risky_disks)} risky disks (raw or independent persistent)")
    return {
        'count': len(risky_disks),
        'data': risky_disks,
        'details': {
            'blocking_risks': blocking_count,
            'warning_risks': warning_count,
            'has_physical_mode_rdm': blocking_count > 0,
            'card_risk': card_risk
        }
    }


@risk_info(
    level='blocking',
    description='This shows the distribution of VMs and ports using dvSwitches or standard vSwitches.',
    alert_message="""HCX network extension functionality requires the use of distributed switches (dvSwitches).
    In case of standard vSwitches, the migration process will be more complex."""
)
def detect_non_dvs_switches(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect non-dvSwitch network interfaces."""
    if 'vNetwork' not in excel_data.sheet_names:
        logger.warning("detect_non_dvs_switches: No vNetwork sheet found")
        return {'count': 0, 'data': []}

    vnetwork_data = excel_data.parse('vNetwork')

    # Filter out rows with empty or null Switch values
    vnetwork_data = vnetwork_data[vnetwork_data['Switch'].notna() & (vnetwork_data['Switch'] != '')]

    # Get list of distributed switches (empty list if no dvSwitch sheet exists)
    dvswitch_list = []
    if 'dvSwitch' in excel_data.sheet_names:
        dvswitch_data = excel_data.parse('dvSwitch')
        if 'Switch' in dvswitch_data.columns:
            dvswitch_list = dvswitch_data['Switch'].dropna().unique()

    # Add Switch Type classification
    vnetwork_data['Switch Type'] = vnetwork_data['Switch'].apply(
        lambda x: 'Standard' if x not in dvswitch_list else 'Distributed'
    )

    # Count ports per switch with switch type
    switch_summary = vnetwork_data.groupby(['Switch', 'Switch Type']).size().reset_index(name='Port Count')

    # Convert to list of dictionaries for consistent API response
    switch_data = switch_summary.to_dict(orient='records')

    # Count VMs using non-distributed switches (individual ports/VMs)
    non_dvs_vm_count = len(vnetwork_data[vnetwork_data['Switch Type'] == 'Standard'])

    logger.info(f"detect_non_dvs_switches: Found {non_dvs_vm_count} VMs using standard switches")
    if non_dvs_vm_count > 0:
        return {
            'count': non_dvs_vm_count,  # Count of VMs using standard switches
            'data': switch_data         # Summary data by switch
        }
    else:
        return {'count': 0, 'data': []}


@risk_info(
    level='warning',
    description='vSnapshots are virtual machine snapshots that capture the state of a VM at a specific point in time.',
    alert_message="""Having multiple vSnapshots can pose a risk during migration, as they can increase complexity
    and may lead to data loss if not handled properly.<br><br>It's recommended to review and consider
    consolidating or deleting unnecessary snapshots."""
)
def detect_snapshots(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VM snapshots with password redaction for security."""
    if 'vSnapshot' not in excel_data.sheet_names:
        logger.warning("detect_snapshots: No vSnapshot sheet found")
        return {'count': 0, 'data': []}

    vsnapshot_sheet = excel_data.parse('vSnapshot')

    # Process snapshots and redact any descriptions containing passwords
    snapshots = []
    for _, row in vsnapshot_sheet.iterrows():
        snapshot_data = {
            'VM': row.get('VM', ''),
            'Powerstate': row.get('Powerstate', ''),
            'Name': row.get('Name', ''),
            'Date / time': row.get('Date / time', ''),
            'Size MiB (vmsn)': row.get('Size MiB (vmsn)', ''),
            'Description': redact_password_content(row.get('Description', ''))
        }
        snapshots.append(snapshot_data)

    logger.info(f"detect_snapshots: Found {len(snapshots)} VM snapshots")
    return {
        'count': len(snapshots),
        'data': snapshots
    }


@risk_info(
    level='warning',
    description='Suspended VMs are virtual machines that are not currently running but have their state saved.',
    alert_message="""Suspended VMs can pose a risk during migration, as it will be necessary to power them on
    or off before proceeding.<br><br>It's recommended to review the list of suspended VMs and consider
    powering them on or off."""
)
def detect_suspended_vms(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect suspended VMs."""
    if 'vInfo' not in excel_data.sheet_names:
        logger.warning("detect_suspended_vms: No vInfo sheet found")
        return {'count': 0, 'data': []}

    vinfo_data = excel_data.parse('vInfo')
    suspended_vms = vinfo_data[vinfo_data['Powerstate'] == 'Suspended'][['VM']].to_dict(orient='records')

    logger.info(f"detect_suspended_vms: Found {len(suspended_vms)} suspended VMs")
    return {
        'count': len(suspended_vms),
        'data': suspended_vms
    }


@risk_info(
    level='info',
    description='Oracle VMs are virtual machines specifically configured to run Oracle software.',
    alert_message="""Oracle VMs hosting in Azure VMware Solution is supported but may require costly licensing.
    <br><br>It's recommended to review the list of Oracle VMs and envision alternative hosting options to
    avoid unnecessary costs."""
)
def detect_oracle_vms(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect Oracle VMs."""
    if 'vInfo' not in excel_data.sheet_names:
        logger.warning("detect_oracle_vms: No vInfo sheet found")
        return {'count': 0, 'data': []}

    vinfo_data = excel_data.parse('vInfo')
    oracle_vms = vinfo_data[
        vinfo_data['OS according to the VMware Tools'].str.contains('Oracle', na=False)
    ][['VM', 'OS according to the VMware Tools', 'Powerstate', 'CPUs', 'Memory', 'Provisioned MiB', 'In Use MiB']].to_dict(orient='records')

    logger.info(f"detect_oracle_vms: Found {len(oracle_vms)} Oracle VMs")
    return {
        'count': len(oracle_vms),
        'data': oracle_vms
    }


@risk_info(
    level='warning',
    description='dvPort issues are related to the configuration of distributed virtual ports in a VMware environment.',
    alert_message="""Multiple dvPort issues can pose a risk during migration:
    <ul>
        <li>VLAN ID 0 or empty — they cannot be extended via HCX.</li>
        <li>Allow Promiscuous mode enabled — This configuration may require additional setup on destination side.</li>
        <li>Mac Changes enabled — This configuration may require additional setup on destination side.</li>
        <li>Forged Transmits enabled — This configuration may require additional setup on destination side.</li>
        <li>Ephemeral binding — VMs will be migrated with NIC being disconnected.</li>
    </ul>"""
)
def detect_dvport_issues(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect dvPort configuration issues."""
    if 'dvPort' not in excel_data.sheet_names:
        logger.warning("detect_dvport_issues: No dvPort sheet found")
        return {'count': 0, 'data': []}

    dvport_data = excel_data.parse('dvPort')

    # Store original VLAN nulls before filling
    vlan_is_null = dvport_data['VLAN'].isnull()
    dvport_data['VLAN'] = dvport_data['VLAN'].fillna(0).astype(int)

    # Filter using string comparison directly - no copy needed
    mask = (
        vlan_is_null |
        (dvport_data['Allow Promiscuous'].astype(str).str.lower() == 'true') |
        (dvport_data['Mac Changes'].astype(str).str.lower() == 'true') |
        (dvport_data['Forged Transmits'].astype(str).str.lower() == 'true') |
        (dvport_data['Type'].astype(str).str.lower() == 'ephemeral')
    )

    # Return original values directly
    columns_to_return = ['Port', 'Switch', 'Object ID', 'VLAN', 'Allow Promiscuous', 'Mac Changes', 'Forged Transmits', 'Type']
    available_columns = [col for col in columns_to_return if col in dvport_data.columns]

    issues = dvport_data[mask][available_columns].to_dict(orient='records')

    logger.info(f"detect_dvport_issues: Found {len(issues)} dvPort configuration issues")
    return {
        'count': len(issues),
        'data': issues
    }


@risk_info(
    level='warning',
    description='Hosts with CPU models that are not Intel may pose compatibility issues during migration.',
    alert_message="""As Azure VMware Solution is Intel based service, a cold migration strategy will be
    required for the workload in these hosts."""
)
def detect_non_intel_hosts(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect non-Intel CPU hosts."""
    if 'vHost' not in excel_data.sheet_names:
        logger.warning("detect_non_intel_hosts: No vHost sheet found")
        return {'count': 0, 'data': []}

    vhost_data = excel_data.parse('vHost')
    non_intel_hosts = vhost_data[
        ~vhost_data['CPU Model'].str.contains('Intel', na=False)
    ][['Host', 'Datacenter', 'Cluster', 'CPU Model', '# VMs']].to_dict(orient='records')

    logger.info(f"detect_non_intel_hosts: Found {len(non_intel_hosts)} non-Intel hosts")
    return {
        'count': len(non_intel_hosts),
        'data': non_intel_hosts
    }


@risk_info(
    level='warning',
    description='VMs that are powered on but their VMware Tools are not running.',
    alert_message="""VMs without VMware Tools running may not be able to use all the features of VMware HCX
    during migration.<br><br>It's recommended to ensure that VMware Tools are installed, running and
    up-to-date on all powered-on VMs."""
)
def detect_vmtools_not_running(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with VMware Tools not running."""
    if 'vInfo' not in excel_data.sheet_names:
        logger.warning("detect_vmtools_not_running: No vInfo sheet found")
        return {'count': 0, 'data': []}

    vinfo_data = excel_data.parse('vInfo')
    vmtools_issues = vinfo_data[
        (vinfo_data['Powerstate'] == 'poweredOn') &
        (vinfo_data['Guest state'] == 'notRunning')
    ][['VM', 'Powerstate', 'Guest state', 'OS according to the configuration file']].to_dict(orient='records')

    logger.info(f"detect_vmtools_not_running: Found {len(vmtools_issues)} VMs with VMware Tools not running")
    return {
        'count': len(vmtools_issues),
        'data': vmtools_issues
    }


@risk_info(
    level='warning',
    description='VMs have CD-ROM devices that are connected.',
    alert_message="""CD-ROM devices connected to VMs can cause issues during migration. It's recommended to
    review and disconnect unnecessary CD-ROM devices before proceeding."""
)
def detect_cdrom_issues(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect mounted CD/DVD drives."""
    if 'vCD' not in excel_data.sheet_names:
        logger.warning("detect_cdrom_issues: No vCD sheet found")
        return {'count': 0, 'data': []}

    vcd_data = excel_data.parse('vCD')

    # Filter using string comparison directly - no copy needed
    mask = vcd_data['Connected'].astype(str).str.lower() == 'true'

    # Select only columns that exist in the data
    columns_to_return = ['VM', 'Powerstate', 'Connected', 'Starts Connected', 'Device Type']
    available_columns = [col for col in columns_to_return if col in vcd_data.columns]

    cdrom_issues = vcd_data[mask][available_columns].to_dict(orient='records')

    logger.info(f"detect_cdrom_issues: Found {len(cdrom_issues)} VMs with connected CD-ROM devices")
    return {
        'count': len(cdrom_issues),
        'data': cdrom_issues
    }


@risk_info(
    level=RiskLevel.WARNING,
    description='VMs have provisioned storage exceeding 10TB.',
    alert_message="""Large provisioned storage can lead to increased migration times and potential compatibility
    issues. It's recommended to review these VMs and optimize storage usage if possible."""
)
def detect_large_provisioned_vms(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with large provisioned disks (>10TB)."""
    vm_data = safe_sheet_access(excel_data, 'vInfo')
    if vm_data is None:
        logger.warning("detect_large_provisioned_vms: No vInfo sheet found")
        return create_empty_result()

    vm_data['Provisioned MiB'] = pd.to_numeric(vm_data['Provisioned MiB'], errors='coerce')
    vm_data['In Use MiB'] = pd.to_numeric(vm_data['In Use MiB'], errors='coerce')
    vm_data['Provisioned TB'] = (vm_data['Provisioned MiB'] * 1.048576) / (1024 * 1024)

    large_vms = vm_data[vm_data['Provisioned TB'] > 10][
        ['VM', 'Provisioned MiB', 'In Use MiB', 'CPUs', 'Memory']
    ].to_dict(orient='records')

    logger.info(f"detect_large_provisioned_vms: Found {len(large_vms)} VMs with >10TB provisioned storage")
    return {
        'count': len(large_vms),
        'data': large_vms
    }


@risk_info(
    level=RiskLevel.BLOCKING,
    description='VMs have a vCPU count higher than the core count of available SKUs.',
    alert_message="""The VMs with more vCPUs configured than the available SKUs core count will not be able
    to run on the target hosts."""
)
def detect_high_vcpu_vms(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with high vCPU count."""
    vm_data = safe_sheet_access(excel_data, 'vInfo')
    if vm_data is None:
        logger.warning("detect_high_vcpu_vms: No vInfo sheet found")
        return create_empty_result()

    # Load SKU data using cached function
    try:
        sku_data = load_sku_data()
    except FileNotFoundError:
        logger.warning("detect_high_vcpu_vms: SKU data file not found")
        return {'count': 0, 'data': [], 'error': 'SKU data file not found'}

    vm_data['CPUs'] = pd.to_numeric(vm_data['CPUs'], errors='coerce')

    sku_cores = {sku['name']: sku['cores'] for sku in sku_data}
    min_cores = min(sku_cores.values())

    high_vcpu_vms = []
    for _, vm in vm_data.iterrows():
        if vm['CPUs'] > min_cores:
            vm_data_entry = {
                'VM': vm['VM'],
                'vCPU Count': vm['CPUs'],
                **{sku: False if vm['CPUs'] > cores else True for sku, cores in sku_cores.items()}
            }
            high_vcpu_vms.append(vm_data_entry)

    logger.info(f"detect_high_vcpu_vms: Found {len(high_vcpu_vms)} VMs with high vCPU count (>{min_cores} cores)")
    return {
        'count': len(high_vcpu_vms),
        'data': high_vcpu_vms
    }


@risk_info(
    level=RiskLevel.BLOCKING,
    description='VMs have memory usage exceeding the capabilities of available SKUs.',
    alert_message="""The VMs with more memory configured than the available capacity per node will not be able
    to run on the target hosts.<br><br>For performance best practices, it is also recommended not to exceed
    half of the available memory per node on a single VM."""
)
def detect_high_memory_vms(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with high memory allocation."""
    vm_data = safe_sheet_access(excel_data, 'vInfo')
    if vm_data is None:
        logger.warning("detect_high_memory_vms: No vInfo sheet found")
        return create_empty_result()

    # Load SKU data using cached function
    try:
        sku_data = load_sku_data()
    except FileNotFoundError:
        logger.error("detect_high_memory_vms: SKU data file not found")
        return {'count': 0, 'data': [], 'error': 'SKU data file not found'}

    vm_data['Memory'] = pd.to_numeric(vm_data['Memory'], errors='coerce')

    min_memory = min(sku['ram'] * 1024 for sku in sku_data)

    high_memory_vms = []
    for _, vm in vm_data.iterrows():
        if vm['Memory'] > min_memory:
            vm_data_entry = {
                'VM': vm['VM'],
                'Memory (GB)': round(vm['Memory'] / 1024, 2),
                **{
                    sku['name']: False if vm['Memory'] > sku['ram'] * 1024 else True
                    for sku in sku_data
                }
            }
            high_memory_vms.append(vm_data_entry)

    logger.info(f"detect_high_memory_vms: Found {len(high_memory_vms)} VMs with high memory allocation (>{min_memory / 1024} GB)")
    return {
        'count': len(high_memory_vms),
        'data': high_memory_vms
    }


@risk_info(
    level=RiskLevel.BLOCKING,
    description="""Virtual machines with legacy hardware version have limited HCX migration capabilities to Azure VMware Solution.
    Hardware version determines the virtual machine's feature set and migration compatibility.""",
    alert_message="""You should consider upgrading the hardware version of these VMs before migration.
    This requires powering off the VM temporarily."""
)
def detect_hw_version_compatibility(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with incompatible hardware versions for Azure VMware Solution migration."""
    vm_data = safe_sheet_access(excel_data, 'vInfo')
    if vm_data is None:
        logger.warning("detect_hw_version_compatibility: No vInfo sheet found")
        return create_empty_result()

    # Check if HW version column exists
    if 'HW version' not in vm_data.columns:
        logger.warning("detect_hw_version_compatibility: HW version column not found in vInfo sheet")
        return create_empty_result()

    # Filter VMs with hardware version issues
    incompatible_vms = []

    for _, vm in vm_data.iterrows():
        hw_version = vm.get('HW version')
        if pd.isna(hw_version):
            continue

        try:
            # Convert to numeric value
            hw_version_num = int(hw_version)

            # Get migration configuration
            migration_config = MigrationMethodsConfig()

            # Determine unsupported migration methods based on HW version
            unsupported_methods = []

            # Check if HW version is below minimum supported version
            if hw_version_num < migration_config.minimum_supported_hw_version:
                unsupported_methods = [migration_config.all_methods_unsupported_message]
            else:
                # Check each migration method against its minimum requirement
                for method_name, min_hw_version in migration_config.migration_methods.items():
                    if hw_version_num < min_hw_version:
                        unsupported_methods.append(method_name)

            # Only include VMs with migration limitations
            if unsupported_methods:
                vm_info = {
                    'VM': vm.get('VM', 'Unknown'),
                    'HW Version': hw_version,
                    'Powerstate': vm.get('Powerstate', 'Unknown'),
                    'Unsupported migration methods': ', '.join(unsupported_methods)
                }
                incompatible_vms.append(vm_info)

        except (ValueError, TypeError) as e:
            logger.error(f"Could not parse hardware version '{hw_version}' for VM {vm.get('VM', 'Unknown')}: {e}")
            continue

    logger.info(f"detect_hw_version_compatibility: Found {len(incompatible_vms)} VMs with hardware version compatibility issues")
    return {
        'count': len(incompatible_vms),
        'data': incompatible_vms
    }


@risk_info(
    level=RiskLevel.BLOCKING,
    description='VMs sharing disks with the same path cannot be migrated.',
    alert_message="""Virtual machines cannot be migrated while they are using a shared SCSI bus,
    flagged for multi-writer, or configured for shared VMDK disk sharing."""
)
def detect_shared_disks(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """Detect VMs with shared disk configurations."""
    vdisk_data = safe_sheet_access(excel_data, 'vDisk')
    if vdisk_data is None:
        logger.warning("detect_shared_disks: No vDisk sheet found")
        return create_empty_result()

    # Check if required columns exist
    required_columns = ['VM', 'Path']
    if not all(col in vdisk_data.columns for col in required_columns):
        logger.warning("detect_shared_disks: Required columns not found in vDisk sheet for shared disk detection")
        return create_empty_result()

    # Filter out rows with null or empty paths
    vdisk_data = vdisk_data[vdisk_data['Path'].notna() & (vdisk_data['Path'] != '')]

    # Group by path to find shared disks (same path used by multiple VMs)
    path_groups = vdisk_data.groupby('Path')

    shared_disk_groups = []
    detected_vms = set()  # Track VMs already detected to avoid duplicates

    # First, detect VMs sharing the same disk path
    for path, group in path_groups:
        unique_vms = group['VM'].unique()
        if len(unique_vms) > 1:  # Multiple VMs sharing the same path
            # Create a summary for this shared disk group
            shared_disk_info = {
                'Path': path,
                'VM Count': len(unique_vms),
                'VMs': ', '.join(sorted(unique_vms))
            }

            # Add VMs to detected set
            detected_vms.update(unique_vms)

            # Add optional information if available
            if 'Sharing mode' in group.columns:
                sharing_modes = group['Sharing mode'].dropna()
                if len(sharing_modes) > 0:
                    # Take the first non-empty value as example
                    shared_disk_info['Sharing mode'] = str(sharing_modes.iloc[0])
                else:
                    shared_disk_info['Sharing mode'] = ''

            if 'Write Through' in group.columns:
                write_through_values = group['Write Through'].dropna()
                if len(write_through_values) > 0:
                    # Take the first non-empty value as example
                    shared_disk_info['Write Through'] = str(write_through_values.iloc[0])
                else:
                    shared_disk_info['Write Through'] = ''

            if 'Shared Bus' in group.columns:
                shared_bus_values = group['Shared Bus'].dropna()
                if len(shared_bus_values) > 0:
                    # Take the first non-empty value as example
                    shared_disk_info['Shared Bus'] = str(shared_bus_values.iloc[0])
                else:
                    shared_disk_info['Shared Bus'] = ''

            shared_disk_groups.append(shared_disk_info)

    # Second, detect VMs with Shared Bus != "noSharing" that weren't already detected
    if 'Shared Bus' in vdisk_data.columns:
        # Filter for VMs with sharing bus configurations that are not "noSharing"
        sharing_bus_mask = (
            vdisk_data['Shared Bus'].notna() &
            (vdisk_data['Shared Bus'] != '') &
            (vdisk_data['Shared Bus'].astype(str).str.lower() != 'nosharing')
        )

        sharing_bus_vms = vdisk_data[sharing_bus_mask]

        for _, row in sharing_bus_vms.iterrows():
            vm_name = row['VM']
            if vm_name not in detected_vms:  # Only add if not already detected
                shared_disk_info = {
                    'Path': row['Path'],
                    'VM Count': 1,
                    'VMs': vm_name
                }

                # Add the specific sharing information in the correct order
                if 'Sharing mode' in row.index:
                    shared_disk_info['Sharing mode'] = str(row['Sharing mode']) if pd.notna(row['Sharing mode']) else ''

                if 'Write Through' in row.index:
                    shared_disk_info['Write Through'] = str(row['Write Through']) if pd.notna(row['Write Through']) else ''

                shared_disk_info['Shared Bus'] = str(row['Shared Bus'])

                shared_disk_groups.append(shared_disk_info)

                detected_vms.add(vm_name)

    logger.info(f"detect_shared_disks: Found {len(shared_disk_groups)} shared disk configurations")
    return {
        'count': len(shared_disk_groups),
        'data': shared_disk_groups
    }


@risk_info(
    level=RiskLevel.EMERGENCY,
    description='Critical security risk: Clear text passwords detected in VM annotations or snapshot descriptions.',
    alert_message="""<strong>CRITICAL SECURITY ALERT:</strong> Clear text passwords have been detected in your VMware environment!
                    This poses a significant security risk. Passwords may have been found in:
                    <ul>
                        <li>VM Annotations (vInfo sheet)</li>
                        <li>Snapshot Descriptions (vSnapshot sheet)</li>
                    </ul>
                    <strong>IMPORTANT:</strong> This RVTools file has NOT been stored on our server and was analyzed in memory only.
                    However, you should immediately:
                    <ul>
                        <li>Remove all clear text passwords from VM annotations and snapshot descriptions</li>
                        <li>Rotate any exposed passwords</li>
                        <li>Implement secure password management practices</li>
                        <li>Review access to your vCenter environment</li>
                    </ul>"""
)
def detect_clear_text_passwords(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """
    Detect clear text passwords in VM annotations and snapshot descriptions.

    This is a critical security risk detection that scans for any form of "password"
    in VM annotations (vInfo sheet) and snapshot descriptions (vSnapshot sheet).

    Args:
        excel_data: Parsed Excel file from RVTools

    Returns:
        Dictionary with count and data about VMs/snapshots containing passwords
    """
    password_exposures = []

    # Check vInfo sheet for passwords in Annotation column
    vinfo_data = safe_sheet_access(excel_data, 'vInfo')
    if not vinfo_data.empty and 'Annotation' in vinfo_data.columns:
        # Filter rows where Annotation contains password-like terms
        vinfo_with_annotations = vinfo_data[vinfo_data['Annotation'].notna() & (vinfo_data['Annotation'] != '')]

        for _, row in vinfo_with_annotations.iterrows():
            annotation = str(row['Annotation'])
            if contains_password_reference(annotation):
                password_exposures.append({
                    'Source': 'VM Annotation',
                    'VM Name': row.get('VM', 'Unknown'),
                    'Snapshot Name': '',  # N/A for VM annotations
                    'Location Type': 'vInfo Sheet',
                    'Risk Level': 'emergency',
                    'Details': f"Password reference found in VM annotations."
                })

    # Check vSnapshot sheet for passwords in Description column
    vsnapshot_data = safe_sheet_access(excel_data, 'vSnapshot')
    if not vsnapshot_data.empty and 'Description' in vsnapshot_data.columns:
        # Filter rows where Description contains password-like terms
        vsnapshot_with_descriptions = vsnapshot_data[vsnapshot_data['Description'].notna() & (vsnapshot_data['Description'] != '')]

        for _, row in vsnapshot_with_descriptions.iterrows():
            description = str(row['Description'])
            if contains_password_reference(description):
                password_exposures.append({
                    'Source': 'Snapshot Description',
                    'VM Name': row.get('VM', 'Unknown'),
                    'Snapshot Name': row.get('Snapshot', 'Unknown'),
                    'Location Type': 'vSnapshot Sheet',
                    'Risk Level': 'emergency',
                    'Details': f"Password reference found in snapshot description."
                })

    return {'count': len(password_exposures), 'data': password_exposures}


########################################################################################################################
#                                                                                                                      #
#                                         End of Risk Detection Functions                                              #
#                                                                                                                      #
########################################################################################################################


def get_risk_functions_list() -> List:
    """
    Get the centralized list of all risk detection functions.

    Returns:
        List of risk detection functions
    """
    return [
        detect_esx_versions,
        detect_vusb_devices,
        detect_risky_disks,
        detect_non_dvs_switches,
        detect_snapshots,
        detect_suspended_vms,
        detect_oracle_vms,
        detect_dvport_issues,
        detect_non_intel_hosts,
        detect_vmtools_not_running,
        detect_cdrom_issues,
        detect_large_provisioned_vms,
        detect_high_vcpu_vms,
        detect_high_memory_vms,
        detect_hw_version_compatibility,
        detect_shared_disks,
        detect_clear_text_passwords,
    ]

def get_total_risk_functions_count() -> int:
    """
    Get the total number of available risk detection functions.

    Returns:
        Integer count of risk detection functions
    """
    return len(get_risk_functions_list())

def get_available_risks() -> Dict[str, Any]:
    """
    Get information about all available risk detection functions.

    Returns:
        Dictionary containing metadata about each risk detection function
    """
    risk_functions = get_risk_functions_list()

    available_risks = {}
    risk_levels_count = {'info': 0, 'warning': 0, 'danger': 0, 'blocking': 0, 'emergency': 0}

    for func in risk_functions:
        func_name = func.__name__
        risk_metadata = getattr(func, '_risk_info', {})

        # Clean up the function name for display
        # (now using helper function)

        available_risks[func_name] = {
            'name': func_name,
            'display_name': clean_function_name_for_display(func_name),
            'risk_level': risk_metadata.get('level', 'info'),
            'description': risk_metadata.get('description', 'No description available'),
            'alert_message': risk_metadata.get('alert_message', None),
            'category': get_risk_category(func_name)
        }

        # Count risk levels
        risk_level = risk_metadata.get('level', 'info')
        if risk_level in risk_levels_count:
            risk_levels_count[risk_level] += 1

    return {
        'total_available_risks': len(available_risks),
        'risk_levels_distribution': risk_levels_count,
        'risks': available_risks
    }

def gather_all_risks(excel_data: pd.ExcelFile) -> Dict[str, Any]:
    """
    Gather all risk detection results.

    Args:
        excel_data: Parsed Excel file from RVTools

    Returns:
        Dictionary containing all risk detection results
    """
    risk_functions = get_risk_functions_list()

    results = {}
    summary = {'total_risks': 0, 'risk_levels': {'info': 0, 'warning': 0, 'danger': 0, 'blocking': 0, 'emergency': 0}}

    for func in risk_functions:
        try:
            result = func(excel_data)
            function_name = result.get('function_name', func.__name__)
            risk_level_val = result.get('risk_level', 'info')

            results[function_name] = result

            if result['count'] > 0:
                summary['total_risks'] += result['count']
                summary['risk_levels'][risk_level_val] += result['count']

        except Exception as e:
            results[func.__name__] = {
                'count': 0,
                'data': [],
                'error': str(e),
                'risk_level': 'info',
                'function_name': func.__name__
            }

    return {
        'summary': summary,
        'risks': results
    }
