#!/usr/bin/env python3
"""
Donjon Platform - Compliance Framework Mapper
Maps security findings to compliance controls across all major frameworks.
Exceeds requirements for NIST, HIPAA, SOC 1/2, PCI-DSS, ISO 27001, etc.
"""

import glob
import os
from dataclasses import dataclass
from typing import Dict, List, Optional, Set
from enum import Enum


# ---------------------------------------------------------------------------
# Attestation Types Taxonomy
# Maps attestation document types to the compliance controls they satisfy.
# Each entry: attestation_type -> {description, controls: {framework -> [control_ids]}}
# ---------------------------------------------------------------------------

ATTESTATION_TYPES: Dict[str, dict] = {
    'policy_document': {
        'description': 'Formal policy document (access control, security, acceptable use, etc.)',
        'controls': {
            'nist_800_53': ['AC-1', 'AT-1', 'AU-1', 'CA-1', 'CM-1', 'CP-1',
                            'IA-1', 'IR-1', 'MA-1', 'MP-1', 'PE-1', 'PL-1',
                            'PM-1', 'PS-1', 'RA-1', 'SA-1', 'SC-1', 'SI-1'],
            'hipaa': ['164.308(a)(1)', '164.310(b)'],
            'pci_dss_4': ['12.1'],
            'soc2': ['CC1.1', 'CC5.1'],
            'iso_27001_2022': ['5.1'],
            # GDPR YAML uses Art.XX IDs
            'gdpr': ['Art.24', 'Art.25', 'Art.32'],
            'ccpa': ['1798.100', '1798.105', '1798.110'],
            # DORA YAML uses art_X IDs
            'dora': ['art_1', 'art_5', 'art_6'],
            # SOX YAML uses SOX-XX-NN IDs
            'sox': ['SOX-AC-01', 'SOX-AC-02', 'SOX-CM-01', 'SOX-CM-02'],
            'fedramp': ['AC-1', 'AT-1', 'CA-1', 'CM-1', 'CP-1', 'IA-1',
                        'IR-1', 'MA-1', 'MP-1', 'PE-1', 'PL-1', 'PS-1',
                        'RA-1', 'SA-1', 'SC-1', 'SI-1'],
            # NIS2 YAML uses art_XX_X IDs
            'nis2': ['art_20_1', 'art_20_2', 'art_21_2_a'],
            # SEC Cyber uses 10-K.106.X IDs
            'sec_cyber': ['10-K.106.b.1', '10-K.106.b.2', '10-K.106.c.1'],
            # UK FCA Resilience
            'uk_fca_resilience': ['board_responsibility', 'self_assessment'],
        },
    },
    'risk_assessment': {
        'description': 'Risk assessment report or risk register',
        'controls': {
            'nist_800_53': ['RA-1', 'RA-3'],
            'hipaa': ['164.308(a)(1)'],
            'pci_dss_4': ['12.1'],
            'soc2': ['CC3.1', 'CC3.2'],
            'iso_27001_2022': ['5.1'],
            'gdpr': ['Art.35'],
            'dora': ['art_1', 'art_5'],
            'nis2': ['art_21_2_a', 'art_21_2_b'],
            'eu_ai_act': ['art_10_2', 'art_10_3'],
            'sec_cyber': ['10-K.106.b.3', '10-K.106.b.4'],
            'sox': ['SOX-RA-01', 'SOX-RA-02'],
            'uk_fca_resilience': ['self_assessment', 'impact_tolerance_setting'],
        },
    },
    'incident_response_plan': {
        'description': 'Incident response plan, playbooks, or tabletop exercise results',
        'controls': {
            'nist_800_53': ['IR-1', 'IR-4', 'IR-6', 'IR-8'],
            'hipaa': ['164.308(a)(6)'],
            'pci_dss_4': ['12.1'],
            'soc2': ['CC7.3', 'CC7.4'],
            'iso_27001_2022': ['5.1'],
            'gdpr': ['Art.33', 'Art.34'],
            'dora': ['art_10', 'art_11', 'art_12'],
            'nis2': ['art_23_1', 'art_23_3', 'art_23_4_a', 'art_23_4_b',
                     'art_23_4_c', 'art_23_4_d', 'art_23_4_e', 'art_23_4_f'],
            'sec_cyber': ['8-K.1.05.a', '8-K.1.05.b'],
            'uk_fca_resilience': ['response_recovery', 'scenario_testing'],
        },
    },
    'training_records': {
        'description': 'Security awareness training records and completion certificates',
        'controls': {
            'nist_800_53': ['AT-2', 'AT-3', 'AT-4'],
            'hipaa': ['164.308(a)(5)'],
            'pci_dss_4': ['12.1'],
            'soc2': ['CC1.1'],
            'gdpr': ['Art.39'],
            'dora': ['art_13', 'art_14'],
            'nis2': ['art_20_2'],
        },
    },
    'access_review': {
        'description': 'Periodic access review results, role recertification',
        'controls': {
            'nist_800_53': ['AC-2', 'AC-6'],
            'hipaa': ['164.308(a)(3)', '164.308(a)(4)'],
            'pci_dss_4': ['7.1', '7.2', '8.1'],
            'soc2': ['CC6.1', 'CC6.2', 'CC6.3'],
            'iso_27001_2022': ['5.15'],
            'SOC1-Type2': ['ITGC-01', 'ITGC-02'],
            'sox': ['SOX-AC-03', 'SOX-AC-04', 'SOX-AC-05'],
        },
    },
    'data_classification': {
        'description': 'Data classification scheme, data inventory, processing records',
        'controls': {
            'nist_800_53': ['RA-2'],
            'gdpr': ['Art.30', 'Art.35'],
            'ccpa': ['1798.100', '1798.100.b', '1798.105', '1798.110',
                     '1798.115', '1798.120', '1798.125', '1798.130'],
            # Privacy frameworks: use actual YAML control IDs
            'colorado_privacy': ['right_access', 'right_delete', 'right_opt_out'],
            'connecticut_cdpa': ['right_access', 'right_delete', 'right_opt_out'],
            'ny_shield': ['admin_1', 'admin_2', 'admin_3'],
            'texas_dpsa': ['right_access', 'right_confirm', 'right_delete'],
            'virginia_cdpa': ['right_access', 'right_know', 'right_delete'],
            'eu_ai_act': ['art_10_2', 'art_10_3', 'art_10_5'],
        },
    },
    'vendor_assessment': {
        'description': 'Third-party/vendor risk assessment, SOC reports from vendors',
        'controls': {
            'nist_800_53': ['SA-4', 'SA-9', 'SR-6'],
            'soc2': ['CC9.1'],
            'pci_dss_4': ['12.1'],
            'gdpr': ['Art.28'],
            'dora': ['art_17', 'art_18', 'art_19'],
        },
    },
    'board_minutes': {
        'description': 'Board/committee meeting minutes showing security governance',
        'controls': {
            'nist_800_53': ['PM-1', 'PM-2'],
            'soc2': ['CC1.1', 'CC4.1'],
            'sox': ['SOX-AC-01', 'SOX-CM-01'],
            'sec_cyber': ['10-K.106.c.1', '10-K.106.c.2',
                          '10-K.106.c.2.i', '10-K.106.c.2.ii'],
            'uk_fca_resilience': ['board_responsibility', 'communication'],
        },
    },
    'backup_test_results': {
        'description': 'Backup and disaster recovery test results',
        'controls': {
            'nist_800_53': ['CP-2', 'CP-9', 'CP-10'],
            'hipaa': ['164.308(a)(7)'],
            'soc2': ['CC9.1'],
            'SOC1-Type2': ['ITGC-08'],
            'iso_27001_2022': ['8.1'],
            'dora': ['art_11', 'art_12'],
            'nis2': ['art_21_2_c'],
            'uk_fca_resilience': ['response_recovery', 'scenario_testing'],
        },
    },
    'business_continuity_plan': {
        'description': 'Business continuity / disaster recovery plan',
        'controls': {
            'nist_800_53': ['CP-1', 'CP-2'],
            'hipaa': ['164.308(a)(7)'],
            'soc2': ['CC9.1'],
            'dora': ['art_11'],
            'nis2': ['art_21_2_c'],
            'uk_fca_resilience': ['response_recovery', 'ibs_identification'],
        },
    },
    'privacy_impact_assessment': {
        'description': 'Privacy impact assessment (PIA / DPIA)',
        'controls': {
            'gdpr': ['Art.35', 'Art.36'],
            'ccpa': ['1798.100'],
            'colorado_privacy': ['right_access', 'right_opt_out'],
            'eu_ai_act': ['art_10_2', 'art_13_1', 'art_14_1'],
        },
    },
    'data_processing_agreement': {
        'description': 'Data processing agreements with processors/sub-processors',
        'controls': {
            'gdpr': ['Art.28', 'Art.26'],
            'ccpa': ['1798.140'],
            'colorado_privacy': ['right_opt_out'],
            'connecticut_cdpa': ['right_opt_out'],
            'virginia_cdpa': ['right_opt_out_sale', 'right_opt_out_targeted'],
        },
    },
    'consent_records': {
        'description': 'Records of consent collection and management',
        'controls': {
            'gdpr': ['Art.6', 'Art.7', 'Art.9'],
            'ccpa': ['1798.120', '1798.121', '1798.135'],
            'colorado_privacy': ['right_opt_out'],
        },
    },
    'penetration_test_report': {
        'description': 'Penetration test report from qualified assessor',
        'controls': {
            'nist_800_53': ['CA-8', 'RA-5'],
            'pci_dss_4': ['11.4'],
            'soc2': ['CC7.1'],
            'fedramp': ['CA-8', 'RA-5'],
            'dora': ['art_11', 'art_12'],
            'nis2': ['art_21_2_e'],
        },
    },
    'change_management_records': {
        'description': 'Change advisory board records, change tickets, approval logs',
        'controls': {
            'nist_800_53': ['CM-3', 'CM-4'],
            'soc2': ['CC8.1'],
            'SOC1-Type2': ['ITGC-04', 'ITGC-05', 'ITGC-06'],
            'pci_dss_4': ['6.2'],
            'iso_27001_2022': ['8.9'],
            'sox': ['SOX-CM-03', 'SOX-CM-04'],
        },
    },
    'ai_governance': {
        'description': 'AI model governance documentation, bias assessments, model cards',
        'controls': {
            'eu_ai_act': ['art_10_2', 'art_10_3', 'art_10_5', 'art_11_1',
                          'art_12_1', 'art_13_1', 'art_14_1', 'art_14_2'],
            'nist_800_53': ['CM-7'],
        },
    },
    'data_subject_rights_process': {
        'description': 'Documented process for handling data subject rights requests',
        'controls': {
            'gdpr': ['Art.12', 'Art.13', 'Art.14', 'Art.15', 'Art.16',
                     'Art.17', 'Art.18', 'Art.19', 'Art.20', 'Art.21', 'Art.22'],
            'ccpa': ['1798.100', '1798.100.b', '1798.105', '1798.106',
                     '1798.110', '1798.115', '1798.120', '1798.121',
                     '1798.125', '1798.130', '1798.135', '1798.140',
                     '1798.145', '1798.150', '1798.155', '1798.185',
                     '1798.190', '1798.192', '1798.196', '1798.198'],
            'colorado_privacy': ['right_access', 'right_correct', 'right_delete',
                                 'right_opt_out', 'right_portability'],
            'connecticut_cdpa': ['right_access', 'right_appeal', 'right_correct',
                                 'right_delete', 'right_opt_out', 'right_portability'],
            'texas_dpsa': ['right_access', 'right_confirm', 'right_correct',
                           'right_delete', 'right_opt_out_profiling',
                           'right_opt_out_sale', 'right_opt_out_targeted',
                           'right_portability'],
            'virginia_cdpa': ['right_access', 'right_correct', 'right_delete',
                              'right_know', 'right_opt_out_profiling',
                              'right_opt_out_sale', 'right_opt_out_targeted',
                              'right_portability'],
        },
    },
    'security_architecture_review': {
        'description': 'Network diagrams, security architecture documentation, threat models',
        'controls': {
            'nist_800_53': ['PL-2', 'SA-8'],
            'nis2': ['art_21_2_a', 'art_21_2_b', 'art_21_2_d',
                     'art_21_2_e', 'art_21_2_f', 'art_21_2_g',
                     'art_21_2_h', 'art_21_2_i', 'art_21_2_j'],
            'dora': ['art_5', 'art_6', 'art_7', 'art_8', 'art_9'],
            'uk_fca_resilience': ['mapping', 'ibs_identification',
                                  'impact_tolerance_setting'],
            'ny_shield': ['admin_4', 'admin_5', 'admin_6',
                          'phys_1', 'phys_2', 'phys_3',
                          'tech_1', 'tech_2', 'tech_3', 'tech_4', 'tech_5'],
            'sox': ['SOX-AC-06', 'SOX-AC-07', 'SOX-AC-08',
                    'SOX-CM-03', 'SOX-CM-04', 'SOX-CM-05'],
            'sec_cyber': ['10-K.106.b.1', '10-K.106.b.2',
                          '10-K.106.b.3', '10-K.106.b.4'],
            'eu_ai_act': ['art_11_1', 'art_12_1'],
        },
    },
    'supply_chain_security': {
        'description': 'Supply chain risk management, software bill of materials (SBOM)',
        'controls': {
            'nis2': ['art_21_2_d', 'art_21_2_i'],
            'dora': ['art_17', 'art_18', 'art_19'],
            'nist_800_53': ['SR-6', 'SA-4'],
            'eu_ai_act': ['art_14_1', 'art_14_2'],
        },
    },
    'data_protection_principles': {
        'description': 'Documentation of data protection principles (lawfulness, fairness, minimization, accuracy, storage limitation, integrity)',
        'controls': {
            'gdpr': ['Art.5.1.a', 'Art.5.1.b', 'Art.5.1.c', 'Art.5.1.d',
                     'Art.5.1.e', 'Art.5.1.f', 'Art.5.2'],
        },
    },
    'dpo_appointment': {
        'description': 'Data Protection Officer appointment, qualifications, and reporting lines',
        'controls': {
            'gdpr': ['Art.37', 'Art.38', 'Art.39'],
        },
    },
    'international_transfer_safeguards': {
        'description': 'Documentation of cross-border data transfer mechanisms (SCCs, BCRs, adequacy)',
        'controls': {
            'gdpr': ['Art.27', 'Art.29', 'Art.31', 'Art.44', 'Art.46', 'Art.49'],
            'dora': ['art_24', 'art_25', 'art_26'],
        },
    },
    'breach_notification_process': {
        'description': 'Documented breach notification procedures and records',
        'controls': {
            'ccpa': ['1798.82', '1798.81.5'],
            'dora': ['art_28', 'art_29', 'art_30'],
            'nis2': ['art_24_1', 'art_25_1', 'art_26_1', 'art_27_1',
                     'art_28_1', 'art_29_1'],
            'sec_cyber': ['8-K.1.05.c', '8-K.1.05.d'],
        },
    },
    'ai_compliance_documentation': {
        'description': 'AI system documentation: conformity assessments, risk classifications, quality management, post-market monitoring',
        'controls': {
            'eu_ai_act': ['art_15_1', 'art_15_3', 'art_15_5',
                          'art_16_a', 'art_16_c', 'art_16_i',
                          'art_17_1', 'art_17_1_b', 'art_17_1_c',
                          'art_17_1_e', 'art_17_1_f', 'art_17_1_g',
                          'art_17_1_h', 'art_29_1', 'art_29_3',
                          'art_40_1', 'art_41_1', 'art_41_3',
                          'art_42_1', 'art_42_2', 'art_43_1',
                          'art_43_2', 'art_43_3', 'art_43_4',
                          'art_47_1', 'art_48_1', 'art_49_1',
                          'art_49_3', 'art_50_1', 'art_50_2',
                          'art_50_4', 'art_52_1', 'art_52_2',
                          'art_52_3', 'art_52_4', 'art_52_5'],
        },
    },
    'financial_controls_documentation': {
        'description': 'Internal financial control documentation, audit trails, segregation of duties',
        'controls': {
            'sox': ['SOX-AL-01', 'SOX-AL-02', 'SOX-AL-03', 'SOX-AL-04',
                    'SOX-CM-05', 'SOX-CM-06',
                    'SOX-DI-01', 'SOX-DI-02', 'SOX-DI-03', 'SOX-DI-04',
                    'SOX-DI-05', 'SOX-DI-06', 'SOX-DI-07', 'SOX-DI-08',
                    'SOX-DR-01', 'SOX-DR-02',
                    'SOX-IB-01', 'SOX-IB-02',
                    'SOX-LC-01', 'SOX-LC-02', 'SOX-LC-03',
                    'SOX-RA-03'],
        },
    },
    'operational_resilience': {
        'description': 'Operational resilience documentation, critical function mapping, ICT continuity',
        'controls': {
            'dora': ['art_2', 'art_45'],
            'nis2': ['art_21_2_k', 'art_21_2_l', 'art_32_2', 'art_33_2'],
            'uk_fca_resilience': ['vulnerability_identification'],
            'ny_shield': ['phys_4'],
        },
    },
    'privacy_program_documentation': {
        'description': 'Comprehensive privacy program documentation, CPRA regulations compliance',
        'controls': {
            'ccpa': ['1798.140.ag', '1798.140.d', '1798.140.j',
                     '1798.185.a.15', '1798.185.a.16'],
        },
    },
    'ai_sandbox_and_deployer_obligations': {
        'description': 'AI regulatory sandbox documentation, deployer obligations, prohibited AI practices',
        'controls': {
            'eu_ai_act': ['art_4_1', 'art_5_1', 'art_9_1', 'art_9_2', 'art_9_4',
                          'art_17_1_i', 'art_18_1', 'art_19_1',
                          'art_26_1', 'art_26_5', 'art_26_6', 'art_26_8',
                          'art_53_1_a', 'art_53_1_b', 'art_53_1_c', 'art_53_1_d',
                          'art_55_1_a', 'art_55_1_b', 'art_55_1_c', 'art_55_1_d'],
        },
    },
    'sox_operational_controls': {
        'description': 'SOX operational controls: service management, operations, monitoring',
        'controls': {
            'sox': ['SOX-OP-01', 'SOX-OP-02', 'SOX-OP-03', 'SOX-OP-04',
                    'SOX-OP-05', 'SOX-OP-06',
                    'SOX-SM-01', 'SOX-SM-02', 'SOX-SM-03', 'SOX-SM-04',
                    'SOX-SM-05', 'SOX-SM-06', 'SOX-SM-07', 'SOX-SM-08'],
        },
    },
}


class Framework(Enum):
    """Supported compliance frameworks."""
    NIST_800_53 = "nist_800_53"
    NIST_CSF = "nist_csf_2.0"
    NIST_CSF_2_0 = "nist_csf_2.0"
    NIST_800_171 = "nist_800_171"
    HIPAA = "hipaa"
    SOC1_TYPE2 = "SOC1-Type2"
    SOC2_TYPE2 = "soc2"
    PCI_DSS_V4 = "pci_dss_4"
    ISO27001_2022 = "iso_27001_2022"
    CMMC = "cmmc"
    FEDRAMP = "fedramp"
    GDPR = "gdpr"
    SOX = "sox"
    HITRUST = "hitrust-csf"
    CIS = "cis-controls-v8"
    APRA_CPS234 = "apra_cps234"
    CCPA = "ccpa"
    CIS_BENCHMARKS = "cis_benchmarks"
    COLORADO_PRIVACY = "colorado_privacy"
    CONNECTICUT_CDPA = "connecticut_cdpa"
    DORA = "dora"
    EU_AI_ACT = "eu_ai_act"
    NIS2 = "nis2"
    NY_SHIELD = "ny_shield"
    PCI_DSS_4 = "pci_dss_4"
    SEC_CYBER = "sec_cyber"
    SOC2 = "soc2"
    TEXAS_DPSA = "texas_dpsa"
    UK_FCA_RESILIENCE = "uk_fca_resilience"
    VIRGINIA_CDPA = "virginia_cdpa"
    FERPA = "ferpa"
    GLBA = "glba"
    NERC_CIP = "nerc-cip"
    RHEL_CIS = "rhel_cis"


@dataclass
class Control:
    """Represents a compliance control."""
    framework: str
    control_id: str
    control_name: str
    family: str
    description: str
    audit_procedures: List[str]
    evidence_types: List[str]


@dataclass
class ControlMapping:
    """Maps a finding type to controls across frameworks."""
    finding_type: str
    description: str
    controls: Dict[str, List[str]]  # framework -> [control_ids]


class ComplianceMapper:
    """Maps security findings to compliance controls across all frameworks."""

    def __init__(self):
        self._framework_metadata: List[dict] = []
        self._load_control_definitions()
        self._load_mappings()
        self._load_frameworks_from_yaml()
        self._build_control_keyword_index()

    def _load_control_definitions(self):
        """Load control definitions for all frameworks."""
        self.controls: Dict[str, Dict[str, Control]] = {}

        # NIST 800-53 Rev 5 Controls (key security controls)
        self.controls['nist_800_53'] = {
            'AC-1': Control('nist_800_53', 'AC-1', 'Policy and Procedures', 'Access Control',
                           'Access control policy and procedures', ['Policy review', 'Document inspection'],
                           ['policy_document', 'procedure_document']),
            'AC-2': Control('nist_800_53', 'AC-2', 'Account Management', 'Access Control',
                           'Manage system accounts', ['Account listing review', 'Access review'],
                           ['user_list', 'access_report', 'account_audit']),
            'AC-3': Control('nist_800_53', 'AC-3', 'Access Enforcement', 'Access Control',
                           'Enforce approved authorizations', ['Access testing', 'Permission review'],
                           ['access_test_results', 'permission_matrix']),
            'AC-6': Control('nist_800_53', 'AC-6', 'Least Privilege', 'Access Control',
                           'Employ least privilege principle', ['Privilege review', 'Role analysis'],
                           ['privilege_report', 'role_mapping']),
            'AU-2': Control('nist_800_53', 'AU-2', 'Event Logging', 'Audit and Accountability',
                           'Define auditable events', ['Log configuration review'],
                           ['log_config', 'audit_policy']),
            'AU-6': Control('nist_800_53', 'AU-6', 'Audit Record Review', 'Audit and Accountability',
                           'Review and analyze audit records', ['Log review process'],
                           ['log_review_report', 'siem_alerts']),
            'CA-7': Control('nist_800_53', 'CA-7', 'Continuous Monitoring', 'Assessment',
                           'Continuous monitoring strategy', ['Monitoring review'],
                           ['monitoring_report', 'dashboard_screenshot']),
            'CM-2': Control('nist_800_53', 'CM-2', 'Baseline Configuration', 'Configuration Management',
                           'Establish baseline configurations', ['Configuration review'],
                           ['baseline_config', 'config_comparison']),
            'CM-6': Control('nist_800_53', 'CM-6', 'Configuration Settings', 'Configuration Management',
                           'Establish configuration settings', ['Settings review'],
                           ['config_settings', 'hardening_report']),
            'CM-7': Control('nist_800_53', 'CM-7', 'Least Functionality', 'Configuration Management',
                           'Restrict system functions', ['Service review'],
                           ['service_list', 'port_scan']),
            'IA-2': Control('nist_800_53', 'IA-2', 'Identification and Authentication', 'Identification',
                           'Uniquely identify users', ['Authentication testing'],
                           ['auth_config', 'mfa_status']),
            'IA-5': Control('nist_800_53', 'IA-5', 'Authenticator Management', 'Identification',
                           'Manage authenticators', ['Password policy review'],
                           ['password_policy', 'credential_audit']),
            'RA-3': Control('nist_800_53', 'RA-3', 'Risk Assessment', 'Risk Assessment',
                           'Conduct risk assessments', ['Risk assessment review'],
                           ['risk_assessment', 'threat_analysis']),
            'RA-5': Control('nist_800_53', 'RA-5', 'Vulnerability Monitoring and Scanning', 'Risk Assessment',
                           'Monitor and scan for vulnerabilities', ['Vulnerability scan review'],
                           ['vuln_scan_report', 'remediation_tracking']),
            'SC-7': Control('nist_800_53', 'SC-7', 'Boundary Protection', 'System and Communications',
                           'Monitor communications at boundaries', ['Firewall review'],
                           ['firewall_rules', 'network_diagram']),
            'SC-8': Control('nist_800_53', 'SC-8', 'Transmission Confidentiality', 'System and Communications',
                           'Protect transmitted information', ['Encryption testing'],
                           ['ssl_scan', 'encryption_config']),
            'SC-12': Control('nist_800_53', 'SC-12', 'Cryptographic Key Management', 'System and Communications',
                            'Establish cryptographic keys', ['Key management review'],
                            ['key_inventory', 'key_rotation_log']),
            'SC-13': Control('nist_800_53', 'SC-13', 'Cryptographic Protection', 'System and Communications',
                            'Implement cryptography', ['Crypto implementation review'],
                            ['crypto_config', 'cipher_audit']),
            'SI-2': Control('nist_800_53', 'SI-2', 'Flaw Remediation', 'System and Information Integrity',
                           'Identify and correct flaws', ['Patch management review'],
                           ['patch_status', 'remediation_report']),
            'SI-3': Control('nist_800_53', 'SI-3', 'Malicious Code Protection', 'System and Information Integrity',
                           'Implement malicious code protection', ['AV/EDR review'],
                           ['av_status', 'malware_scan']),
            'SI-4': Control('nist_800_53', 'SI-4', 'System Monitoring', 'System and Information Integrity',
                           'Monitor system events', ['Monitoring configuration'],
                           ['siem_config', 'alert_rules']),
        }

        # HIPAA Security Rule Controls
        self.controls['hipaa'] = {
            '164.308(a)(1)': Control('hipaa', '164.308(a)(1)', 'Security Management Process', 'Administrative',
                                    'Implement policies to prevent security violations',
                                    ['Policy review', 'Risk analysis review'],
                                    ['security_policy', 'risk_analysis']),
            '164.308(a)(3)': Control('hipaa', '164.308(a)(3)', 'Workforce Security', 'Administrative',
                                    'Implement procedures for workforce access',
                                    ['Access control procedures review'],
                                    ['access_procedures', 'workforce_access_list']),
            '164.308(a)(4)': Control('hipaa', '164.308(a)(4)', 'Information Access Management', 'Administrative',
                                    'Implement access authorization policies',
                                    ['Authorization review'],
                                    ['access_policy', 'authorization_matrix']),
            '164.308(a)(5)': Control('hipaa', '164.308(a)(5)', 'Security Awareness Training', 'Administrative',
                                    'Security awareness and training program',
                                    ['Training records review'],
                                    ['training_records', 'awareness_materials']),
            '164.308(a)(6)': Control('hipaa', '164.308(a)(6)', 'Security Incident Procedures', 'Administrative',
                                    'Implement security incident procedures',
                                    ['Incident response review'],
                                    ['incident_procedures', 'incident_log']),
            '164.308(a)(7)': Control('hipaa', '164.308(a)(7)', 'Contingency Plan', 'Administrative',
                                    'Establish contingency operations plan',
                                    ['Contingency plan review'],
                                    ['contingency_plan', 'backup_test_results']),
            '164.308(a)(8)': Control('hipaa', '164.308(a)(8)', 'Evaluation', 'Administrative',
                                    'Perform periodic evaluations',
                                    ['Evaluation review'],
                                    ['evaluation_report', 'assessment_results']),
            '164.310(a)(1)': Control('hipaa', '164.310(a)(1)', 'Facility Access Controls', 'Physical',
                                    'Limit physical access to ePHI systems',
                                    ['Physical access review'],
                                    ['access_logs', 'facility_controls']),
            '164.310(b)': Control('hipaa', '164.310(b)', 'Workstation Use', 'Physical',
                                 'Implement workstation use policies',
                                 ['Workstation policy review'],
                                 ['workstation_policy', 'endpoint_config']),
            '164.310(d)(1)': Control('hipaa', '164.310(d)(1)', 'Device and Media Controls', 'Physical',
                                    'Implement device and media controls',
                                    ['Media control review'],
                                    ['media_policy', 'disposal_log']),
            '164.312(a)(1)': Control('hipaa', '164.312(a)(1)', 'Access Control', 'Technical',
                                    'Implement technical access controls',
                                    ['Access control testing'],
                                    ['access_control_config', 'user_access_report']),
            '164.312(b)': Control('hipaa', '164.312(b)', 'Audit Controls', 'Technical',
                                 'Implement audit controls',
                                 ['Audit log review'],
                                 ['audit_config', 'audit_logs']),
            '164.312(c)(1)': Control('hipaa', '164.312(c)(1)', 'Integrity Controls', 'Technical',
                                    'Implement integrity mechanisms',
                                    ['Integrity control review'],
                                    ['integrity_config', 'change_log']),
            '164.312(d)': Control('hipaa', '164.312(d)', 'Person or Entity Authentication', 'Technical',
                                 'Verify identity of persons/entities',
                                 ['Authentication testing'],
                                 ['auth_config', 'identity_verification']),
            '164.312(e)(1)': Control('hipaa', '164.312(e)(1)', 'Transmission Security', 'Technical',
                                    'Implement transmission security',
                                    ['Encryption testing'],
                                    ['encryption_config', 'ssl_scan_results']),
        }

        # PCI-DSS v4.0 Requirements
        self.controls['pci_dss_4'] = {
            '1.1': Control('pci_dss_4', '1.1', 'Network Security Controls', 'Requirement 1',
                          'Install and maintain network security controls',
                          ['Firewall configuration review'],
                          ['firewall_config', 'network_diagram']),
            '1.2': Control('pci_dss_4', '1.2', 'Network Security Control Configuration', 'Requirement 1',
                          'Configure network security controls',
                          ['Ruleset review'],
                          ['firewall_rules', 'acl_config']),
            '2.1': Control('pci_dss_4', '2.1', 'Secure Configurations', 'Requirement 2',
                          'Apply secure configurations',
                          ['Configuration review'],
                          ['hardening_report', 'baseline_comparison']),
            '2.2': Control('pci_dss_4', '2.2', 'System Components Security', 'Requirement 2',
                          'Securely configure system components',
                          ['System configuration review'],
                          ['system_config', 'security_settings']),
            '3.1': Control('pci_dss_4', '3.1', 'Account Data Storage', 'Requirement 3',
                          'Account data storage is kept to minimum',
                          ['Data retention review'],
                          ['data_inventory', 'retention_policy']),
            '3.5': Control('pci_dss_4', '3.5', 'Primary Account Number Protection', 'Requirement 3',
                          'PAN is secured wherever stored',
                          ['Encryption review'],
                          ['encryption_config', 'key_management']),
            '4.1': Control('pci_dss_4', '4.1', 'Strong Cryptography in Transit', 'Requirement 4',
                          'Protect cardholder data with strong cryptography during transmission',
                          ['SSL/TLS testing'],
                          ['ssl_scan', 'certificate_inventory']),
            '5.1': Control('pci_dss_4', '5.1', 'Anti-Malware', 'Requirement 5',
                          'Protect systems from malware',
                          ['AV deployment review'],
                          ['av_status', 'malware_scan_results']),
            '5.2': Control('pci_dss_4', '5.2', 'Anti-Malware Mechanisms', 'Requirement 5',
                          'Anti-malware mechanisms and processes are active',
                          ['AV configuration review'],
                          ['av_config', 'update_status']),
            '6.1': Control('pci_dss_4', '6.1', 'Security Vulnerabilities Identified', 'Requirement 6',
                          'Identify and address security vulnerabilities',
                          ['Vulnerability management review'],
                          ['vuln_scan_report', 'patch_status']),
            '6.2': Control('pci_dss_4', '6.2', 'Custom Software Security', 'Requirement 6',
                          'Bespoke and custom software developed securely',
                          ['Secure development review'],
                          ['code_review', 'sast_results']),
            '6.3': Control('pci_dss_4', '6.3', 'Security Vulnerabilities in Software', 'Requirement 6',
                          'Security vulnerabilities are identified and addressed',
                          ['Vulnerability scanning'],
                          ['vuln_report', 'remediation_tracking']),
            '7.1': Control('pci_dss_4', '7.1', 'Access Limited to Need to Know', 'Requirement 7',
                          'Access to system components limited to those with need',
                          ['Access review'],
                          ['access_matrix', 'role_definitions']),
            '7.2': Control('pci_dss_4', '7.2', 'Access Control Systems', 'Requirement 7',
                          'Access control systems configured to restrict access',
                          ['Access control review'],
                          ['access_control_config', 'permission_audit']),
            '8.1': Control('pci_dss_4', '8.1', 'User Identification', 'Requirement 8',
                          'User identification and related accounts managed',
                          ['Account management review'],
                          ['user_list', 'account_audit']),
            '8.3': Control('pci_dss_4', '8.3', 'Strong Authentication', 'Requirement 8',
                          'Strong authentication for users and administrators',
                          ['Authentication testing'],
                          ['auth_config', 'mfa_status']),
            '10.1': Control('pci_dss_4', '10.1', 'Audit Logs', 'Requirement 10',
                           'Audit logs capture all access',
                           ['Log configuration review'],
                           ['log_config', 'audit_sample']),
            '10.2': Control('pci_dss_4', '10.2', 'Audit Log Content', 'Requirement 10',
                           'Audit logs record required information',
                           ['Log content review'],
                           ['log_sample', 'log_analysis']),
            '10.3': Control('pci_dss_4', '10.3', 'Audit Log Protection', 'Requirement 10',
                           'Audit logs are protected from modification',
                           ['Log protection review'],
                           ['log_integrity', 'access_controls']),
            '11.3': Control('pci_dss_4', '11.3', 'Vulnerabilities Identified', 'Requirement 11',
                           'Vulnerabilities are identified and addressed',
                           ['Vulnerability scanning'],
                           ['vuln_scan_report', 'pentest_report']),
            '11.4': Control('pci_dss_4', '11.4', 'Penetration Testing', 'Requirement 11',
                           'Penetration testing performed regularly',
                           ['Pentest review'],
                           ['pentest_report', 'remediation_status']),
            '12.1': Control('pci_dss_4', '12.1', 'Security Policy', 'Requirement 12',
                           'Security policy is established and maintained',
                           ['Policy review'],
                           ['security_policy', 'policy_acknowledgements']),
        }

        # SOC 2 Trust Services Criteria
        self.controls['soc2'] = {
            'CC1.1': Control('soc2', 'CC1.1', 'Control Environment', 'Common Criteria',
                            'COSO principle: integrity and ethical values',
                            ['Code of conduct review'],
                            ['code_of_conduct', 'ethics_policy']),
            'CC2.1': Control('soc2', 'CC2.1', 'Communication and Information', 'Common Criteria',
                            'COSO principle: generate quality information',
                            ['Information quality review'],
                            ['data_quality_report', 'communication_policy']),
            'CC3.1': Control('soc2', 'CC3.1', 'Risk Assessment', 'Common Criteria',
                            'COSO principle: specify suitable objectives',
                            ['Risk assessment review'],
                            ['risk_assessment', 'objective_documentation']),
            'CC3.2': Control('soc2', 'CC3.2', 'Risk Identification', 'Common Criteria',
                            'Identify and analyze risks',
                            ['Risk identification review'],
                            ['risk_register', 'threat_analysis']),
            'CC4.1': Control('soc2', 'CC4.1', 'Monitoring Activities', 'Common Criteria',
                            'COSO principle: ongoing and separate evaluations',
                            ['Monitoring review'],
                            ['monitoring_report', 'evaluation_schedule']),
            'CC5.1': Control('soc2', 'CC5.1', 'Control Activities', 'Common Criteria',
                            'Select and develop control activities',
                            ['Control activity review'],
                            ['control_matrix', 'control_documentation']),
            'CC6.1': Control('soc2', 'CC6.1', 'Logical Access Security', 'Logical Access',
                            'Implement logical access security software',
                            ['Access control review'],
                            ['access_control_config', 'user_provisioning']),
            'CC6.2': Control('soc2', 'CC6.2', 'Registration and Authorization', 'Logical Access',
                            'Prior to granting access, users are registered',
                            ['User registration review'],
                            ['registration_process', 'authorization_workflow']),
            'CC6.3': Control('soc2', 'CC6.3', 'Access Removal', 'Logical Access',
                            'Remove access when no longer needed',
                            ['Access termination review'],
                            ['termination_process', 'access_review_log']),
            'CC6.6': Control('soc2', 'CC6.6', 'System Boundaries', 'Logical Access',
                            'Restrict access from system boundaries',
                            ['Boundary protection review'],
                            ['firewall_config', 'network_diagram']),
            'CC6.7': Control('soc2', 'CC6.7', 'Data Transmission', 'Logical Access',
                            'Restrict data transmission, movement and removal',
                            ['Data transmission review'],
                            ['encryption_config', 'dlp_config']),
            'CC6.8': Control('soc2', 'CC6.8', 'Malicious Software', 'Logical Access',
                            'Prevent or detect malicious software',
                            ['Malware protection review'],
                            ['av_config', 'edr_status']),
            'CC7.1': Control('soc2', 'CC7.1', 'Vulnerability Detection', 'System Operations',
                            'Detect vulnerabilities in a timely manner',
                            ['Vulnerability scanning review'],
                            ['vuln_scan_report', 'scan_schedule']),
            'CC7.2': Control('soc2', 'CC7.2', 'Security Event Monitoring', 'System Operations',
                            'Monitor system components for anomalies',
                            ['Monitoring review'],
                            ['siem_config', 'alert_review']),
            'CC7.3': Control('soc2', 'CC7.3', 'Security Event Evaluation', 'System Operations',
                            'Evaluate security events to determine impact',
                            ['Event evaluation review'],
                            ['incident_reports', 'event_analysis']),
            'CC7.4': Control('soc2', 'CC7.4', 'Security Incident Response', 'System Operations',
                            'Respond to identified security incidents',
                            ['Incident response review'],
                            ['incident_response_plan', 'incident_log']),
            'CC8.1': Control('soc2', 'CC8.1', 'Change Management', 'Change Management',
                            'Authorize, design, develop, configure, document changes',
                            ['Change management review'],
                            ['change_policy', 'change_log']),
            'CC9.1': Control('soc2', 'CC9.1', 'Risk Mitigation', 'Risk Mitigation',
                            'Identify and assess risks through vendor management',
                            ['Vendor management review'],
                            ['vendor_risk_assessment', 'vendor_inventory']),
        }

        # SOC 1 Type 2 - SSAE 18 ICOFR Controls
        self.controls['SOC1-Type2'] = {
            'ITGC-01': Control('SOC1-Type2', 'ITGC-01', 'Logical Access - User Access Management', 'IT General Controls',
                              'Access to systems is appropriately restricted',
                              ['User access review'],
                              ['user_access_list', 'access_provisioning']),
            'ITGC-02': Control('SOC1-Type2', 'ITGC-02', 'Logical Access - Privileged Access', 'IT General Controls',
                              'Privileged access is restricted and monitored',
                              ['Privileged access review'],
                              ['admin_list', 'privilege_audit']),
            'ITGC-03': Control('SOC1-Type2', 'ITGC-03', 'Logical Access - Authentication', 'IT General Controls',
                              'Authentication mechanisms are in place',
                              ['Authentication testing'],
                              ['auth_config', 'password_policy']),
            'ITGC-04': Control('SOC1-Type2', 'ITGC-04', 'Change Management - Authorization', 'IT General Controls',
                              'Changes are properly authorized before implementation',
                              ['Change authorization review'],
                              ['change_approvals', 'change_log']),
            'ITGC-05': Control('SOC1-Type2', 'ITGC-05', 'Change Management - Testing', 'IT General Controls',
                              'Changes are tested before production deployment',
                              ['Testing review'],
                              ['test_results', 'qa_signoff']),
            'ITGC-06': Control('SOC1-Type2', 'ITGC-06', 'Change Management - Segregation', 'IT General Controls',
                              'Segregation of duties in change management',
                              ['SOD review'],
                              ['role_matrix', 'sod_report']),
            'ITGC-07': Control('SOC1-Type2', 'ITGC-07', 'Operations - Job Scheduling', 'IT General Controls',
                              'Batch jobs are scheduled and monitored',
                              ['Job scheduling review'],
                              ['job_schedule', 'job_logs']),
            'ITGC-08': Control('SOC1-Type2', 'ITGC-08', 'Operations - Backup and Recovery', 'IT General Controls',
                              'Backup and recovery procedures are established',
                              ['Backup review'],
                              ['backup_config', 'restore_test_results']),
            'ITGC-09': Control('SOC1-Type2', 'ITGC-09', 'Operations - Incident Management', 'IT General Controls',
                              'Incidents are tracked and resolved',
                              ['Incident management review'],
                              ['incident_log', 'resolution_tracking']),
        }

        # ISO 27001:2022 Controls
        self.controls['iso_27001_2022'] = {
            '5.1': Control('iso_27001_2022', '5.1', 'Policies for Information Security', 'Organizational',
                          'Information security policy defined and approved',
                          ['Policy review'],
                          ['security_policy', 'policy_approval']),
            '5.15': Control('iso_27001_2022', '5.15', 'Access Control', 'Organizational',
                           'Access control rules established and implemented',
                           ['Access control review'],
                           ['access_policy', 'access_matrix']),
            '5.17': Control('iso_27001_2022', '5.17', 'Authentication Information', 'Organizational',
                           'Management of authentication information',
                           ['Authentication review'],
                           ['auth_policy', 'credential_management']),
            '8.1': Control('iso_27001_2022', '8.1', 'User Endpoint Devices', 'Technological',
                          'Information stored on, processed by or accessible via user endpoint devices',
                          ['Endpoint review'],
                          ['endpoint_policy', 'device_inventory']),
            '8.5': Control('iso_27001_2022', '8.5', 'Secure Authentication', 'Technological',
                          'Secure authentication technologies and procedures',
                          ['Authentication testing'],
                          ['auth_config', 'mfa_status']),
            '8.7': Control('iso_27001_2022', '8.7', 'Protection Against Malware', 'Technological',
                          'Protection against malware implemented',
                          ['Malware protection review'],
                          ['av_config', 'malware_scan']),
            '8.8': Control('iso_27001_2022', '8.8', 'Management of Technical Vulnerabilities', 'Technological',
                          'Timely identification and remediation of vulnerabilities',
                          ['Vulnerability management review'],
                          ['vuln_scan', 'patch_status']),
            '8.9': Control('iso_27001_2022', '8.9', 'Configuration Management', 'Technological',
                          'Configurations established, documented, implemented, monitored',
                          ['Configuration review'],
                          ['config_baseline', 'config_audit']),
            '8.15': Control('iso_27001_2022', '8.15', 'Logging', 'Technological',
                           'Logs recording activities, exceptions, faults, events produced and protected',
                           ['Logging review'],
                           ['log_config', 'log_samples']),
            '8.16': Control('iso_27001_2022', '8.16', 'Monitoring Activities', 'Technological',
                           'Networks, systems, applications monitored for anomalies',
                           ['Monitoring review'],
                           ['siem_config', 'monitoring_report']),
            '8.20': Control('iso_27001_2022', '8.20', 'Networks Security', 'Technological',
                           'Networks secured, managed, controlled',
                           ['Network security review'],
                           ['network_config', 'firewall_rules']),
            '8.21': Control('iso_27001_2022', '8.21', 'Security of Network Services', 'Technological',
                           'Security mechanisms, service levels, management requirements identified',
                           ['Network services review'],
                           ['service_config', 'sla_documentation']),
            '8.24': Control('iso_27001_2022', '8.24', 'Use of Cryptography', 'Technological',
                           'Effective use of cryptography',
                           ['Cryptography review'],
                           ['crypto_config', 'key_management']),
        }

    def _load_frameworks_from_yaml(self):
        """Dynamically load framework YAML files from config/frameworks/ at runtime.

        Handles varying YAML structures (families, domains, functions, themes,
        categories, chapters, sections, requirements, safeguards, articles).
        Supplements hardcoded controls without replacing them.
        """
        try:
            import yaml
        except ImportError:
            return

        # Resolve path relative to this file's location
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        frameworks_dir = os.path.join(base_dir, 'config', 'frameworks')

        if not os.path.isdir(frameworks_dir):
            return

        yaml_files = glob.glob(os.path.join(frameworks_dir, '*.yaml'))
        for yaml_path in sorted(yaml_files):
            try:
                with open(yaml_path, 'r', encoding='utf-8') as f:
                    data = yaml.safe_load(f)
                if not data or not isinstance(data, dict):
                    continue

                fw_meta = data.get('framework', {})
                if not isinstance(fw_meta, dict):
                    continue
                fw_id = str(fw_meta.get('id', ''))
                if not fw_id:
                    continue
                fw_name = str(fw_meta.get('name', fw_id))
                fw_version = str(fw_meta.get('version', ''))

                # Initialize control dict for this framework if not present
                if fw_id not in self.controls:
                    self.controls[fw_id] = {}

                # Extract controls from top-level structural keys
                self._extract_controls_from_data(data, fw_id)

                # Also extract from keys nested under the framework dict
                # (many YAMLs put domains/families/chapters/requirements here)
                self._extract_controls_from_data(fw_meta, fw_id)

                # Store metadata
                self._framework_metadata.append({
                    'id': fw_id,
                    'name': fw_name,
                    'version': fw_version,
                    'control_count': len(self.controls.get(fw_id, {})),
                })
            except Exception:
                continue

    def _extract_controls_from_data(self, data: dict, fw_id: str):
        """Walk top-level grouping keys and extract controls from any structure."""
        # All known top-level grouping keys across the 23 YAML files
        grouping_keys = [
            'families', 'domains', 'functions', 'themes', 'categories',
            'chapters', 'sections', 'requirements', 'safeguards', 'articles',
            'consumer_rights', 'obligations', 'principles',
            'controls', 'practices', 'criteria',
        ]
        for key in grouping_keys:
            container = data.get(key)
            if container is None:
                continue
            if isinstance(container, dict):
                self._walk_dict_groups(container, fw_id)
            elif isinstance(container, list):
                self._walk_list_groups(container, fw_id)

    def _walk_dict_groups(self, groups: dict, fw_id: str, parent_name: str = ''):
        """Walk dict-based groupings (families, domains, themes, etc.)."""
        for group_key, group_val in groups.items():
            if not isinstance(group_val, dict):
                continue
            group_name = group_val.get('name', parent_name or group_key)

            # Look for control-holding sub-keys
            ctrl_sub_keys = [
                'controls', 'practices', 'criteria', 'subcategories',
                'categories', 'safeguards', 'articles',
            ]
            found_controls = False
            for sub_key in ctrl_sub_keys:
                sub = group_val.get(sub_key)
                if sub is None:
                    continue
                if isinstance(sub, dict):
                    # Could be direct controls or another level of nesting
                    has_nested = any(
                        isinstance(v, dict) and any(
                            k2 in v for k2 in ctrl_sub_keys
                        )
                        for v in sub.values() if isinstance(v, dict)
                    )
                    if has_nested:
                        self._walk_dict_groups(sub, fw_id, group_name)
                    else:
                        self._register_controls_from_dict(sub, fw_id, group_name)
                    found_controls = True
                elif isinstance(sub, list):
                    self._walk_list_groups(sub, fw_id, group_name)
                    found_controls = True

            if not found_controls:
                # The group_val itself might contain control-like entries
                # (e.g., articles -> art_X -> controls)
                if 'controls' not in group_val and 'description' in group_val:
                    # This dict IS a control itself
                    self._register_single_control(group_key, group_val, fw_id, parent_name)
                else:
                    # Recurse into values that look like sub-groups
                    for sub_key, sub_val in group_val.items():
                        if isinstance(sub_val, dict) and sub_key not in (
                            'name', 'description', 'policies_required',
                            'policies_recommended', 'sources', 'levels',
                            'implementation_groups', 'impact_levels',
                            'key_concepts', 'definitions', 'applicability',
                            'types',
                        ):
                            if 'controls' in sub_val or 'practices' in sub_val or 'criteria' in sub_val:
                                self._walk_dict_groups({sub_key: sub_val}, fw_id, group_name)

    def _walk_list_groups(self, items: list, fw_id: str, parent_name: str = ''):
        """Walk list-based groupings (some requirements, chapters, consumer_rights)."""
        for item in items:
            if not isinstance(item, dict):
                continue
            item_id = str(item.get('id', ''))
            item_name = item.get('name', parent_name or item_id)

            # Check for nested controls (list or dict)
            ctrl_sub_keys = ['controls', 'articles', 'practices', 'requirements']
            found = False
            for sub_key in ctrl_sub_keys:
                sub = item.get(sub_key)
                if sub is None:
                    continue
                if isinstance(sub, dict):
                    self._register_controls_from_dict(sub, fw_id, item_name)
                    found = True
                elif isinstance(sub, list):
                    self._walk_list_groups(sub, fw_id, item_name)
                    found = True

            if not found and item_id:
                # Item itself is a control
                self._register_single_control(item_id, item, fw_id, parent_name)

    def _register_controls_from_dict(self, controls_dict: dict, fw_id: str, family: str):
        """Register each entry in a controls dict as a Control object."""
        for ctrl_id, ctrl_val in controls_dict.items():
            if not isinstance(ctrl_val, dict):
                continue
            self._register_single_control(str(ctrl_id), ctrl_val, fw_id, family)

    def _register_single_control(self, ctrl_id: str, ctrl_data: dict, fw_id: str, family: str):
        """Register a single control into self.controls, skipping if already present."""
        if ctrl_id in self.controls.get(fw_id, {}):
            return  # Don't overwrite hardcoded controls
        ctrl_name = ctrl_data.get('name', ctrl_id)
        ctrl_desc = ctrl_data.get('description', '')
        if isinstance(ctrl_desc, list):
            ctrl_desc = '; '.join(str(d) for d in ctrl_desc)

        if fw_id not in self.controls:
            self.controls[fw_id] = {}
        self.controls[fw_id][ctrl_id] = Control(
            framework=fw_id,
            control_id=ctrl_id,
            control_name=str(ctrl_name),
            family=str(family),
            description=str(ctrl_desc),
            audit_procedures=[],
            evidence_types=[],
        )

    def get_all_controls(self, framework: str) -> Dict[str, Control]:
        """Get all controls for a specific framework (alias for get_all_controls_for_framework).

        Args:
            framework: Framework ID string.

        Returns:
            Dict mapping control_id to Control objects.
        """
        return self.controls.get(framework, {})

    def check_framework_freshness(self) -> List[dict]:
        """Check age of each framework YAML and flag stale ones.

        Returns list of dicts with framework info and staleness warnings.
        A framework is considered stale if its YAML file hasn't been
        modified in over 90 days.
        """
        import time as _time
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        results = []
        for meta in self._framework_metadata:
            fw_id = meta['id']
            version = meta.get('version', '')
            yaml_path = os.path.join(base_dir, 'config', 'frameworks', f'{fw_id}.yaml')
            if os.path.exists(yaml_path):
                mtime = os.path.getmtime(yaml_path)
                age_days = (_time.time() - mtime) / 86400
                results.append({
                    'id': fw_id,
                    'name': meta.get('name', fw_id),
                    'version': version,
                    'control_count': meta.get('control_count', 0),
                    'file_age_days': round(age_days, 1),
                    'stale': age_days > 90,
                })
        return results

    def get_all_frameworks(self) -> List[dict]:
        """Get metadata for all known frameworks.

        Returns:
            List of dicts with keys: id, name, version, control_count.
            Includes both hardcoded and YAML-loaded frameworks.
        """
        # Build set of framework IDs already captured from YAML metadata
        seen_ids = {m['id'] for m in self._framework_metadata}

        result = list(self._framework_metadata)

        # Include hardcoded frameworks that might not have YAML files
        for fw_key, fw_controls in self.controls.items():
            if fw_key not in seen_ids:
                result.append({
                    'id': fw_key,
                    'name': fw_key,
                    'version': '',
                    'control_count': len(fw_controls),
                })
                seen_ids.add(fw_key)
            else:
                # Update control_count to reflect merged totals
                for entry in result:
                    if entry['id'] == fw_key:
                        entry['control_count'] = len(fw_controls)
                        break

        return result

    # Controls that ANY scanner finding inherently provides evidence for.
    # Every finding from a vulnerability/security scanner demonstrates that
    # vulnerability scanning (RA-5), risk assessment (RA-3), continuous
    # monitoring (CA-7), and flaw remediation tracking (SI-2) are active.
    _UNIVERSAL_NIST_CONTROLS: List[str] = ['RA-5', 'RA-3', 'CA-7']

    def _load_mappings(self):
        """Load finding-to-control mappings."""
        # Maps finding types to controls across all frameworks
        self.mappings: Dict[str, ControlMapping] = {
            'open_port': ControlMapping(
                'open_port',
                'Open network port detected',
                {
                    'nist_800_53': ['CM-7', 'SC-7', 'AC-4'],
                    'hipaa': ['164.312(e)(1)'],
                    'pci_dss_4': ['1.1', '1.2'],
                    'soc2': ['CC6.6'],
                    'SOC1-Type2': ['ITGC-01'],
                    'iso_27001_2022': ['8.20', '8.21']
                }
            ),
            'ssl_vulnerability': ControlMapping(
                'ssl_vulnerability',
                'SSL/TLS vulnerability or misconfiguration',
                {
                    'nist_800_53': ['SC-8', 'SC-12', 'SC-13', 'SC-23'],
                    'hipaa': ['164.312(e)(1)'],
                    'pci_dss_4': ['4.1'],
                    'soc2': ['CC6.7'],
                    'iso_27001_2022': ['8.24']
                }
            ),
            'weak_cipher': ControlMapping(
                'weak_cipher',
                'Weak cryptographic cipher in use',
                {
                    'nist_800_53': ['SC-12', 'SC-13'],
                    'hipaa': ['164.312(e)(1)'],
                    'pci_dss_4': ['4.1', '3.5'],
                    'soc2': ['CC6.7'],
                    'iso_27001_2022': ['8.24']
                }
            ),
            'expired_certificate': ControlMapping(
                'expired_certificate',
                'Expired or soon-to-expire SSL certificate',
                {
                    'nist_800_53': ['SC-12'],
                    'pci_dss_4': ['4.1'],
                    'soc2': ['CC6.7'],
                    'iso_27001_2022': ['8.24']
                }
            ),
            'cve_vulnerability': ControlMapping(
                'cve_vulnerability',
                'Known CVE vulnerability detected',
                {
                    'nist_800_53': ['RA-5', 'SI-2', 'SI-5', 'SA-11'],
                    'hipaa': ['164.308(a)(1)', '164.308(a)(8)'],
                    'pci_dss_4': ['6.1', '6.3', '11.3'],
                    'soc2': ['CC7.1'],
                    'SOC1-Type2': ['ITGC-09'],
                    'iso_27001_2022': ['8.8']
                }
            ),
            'missing_patch': ControlMapping(
                'missing_patch',
                'Missing security patch or update',
                {
                    'nist_800_53': ['SI-2', 'SI-5', 'MA-6'],
                    'hipaa': ['164.308(a)(1)'],
                    'pci_dss_4': ['6.1', '6.3'],
                    'soc2': ['CC7.1'],
                    'iso_27001_2022': ['8.8']
                }
            ),
            'web_vulnerability': ControlMapping(
                'web_vulnerability',
                'Web application vulnerability detected',
                {
                    'nist_800_53': ['RA-5', 'SI-2', 'SA-11', 'SI-10'],
                    'pci_dss_4': ['6.2', '6.3', '11.3'],
                    'soc2': ['CC7.1'],
                    'iso_27001_2022': ['8.8']
                }
            ),
            'authentication_weakness': ControlMapping(
                'authentication_weakness',
                'Weak authentication configuration',
                {
                    'nist_800_53': ['IA-2', 'IA-5', 'IA-6', 'IA-8', 'AC-7'],
                    'hipaa': ['164.312(d)'],
                    'pci_dss_4': ['8.3'],
                    'soc2': ['CC6.1'],
                    'SOC1-Type2': ['ITGC-03'],
                    'iso_27001_2022': ['8.5']
                }
            ),
            'default_credentials': ControlMapping(
                'default_credentials',
                'Default or weak credentials in use',
                {
                    'nist_800_53': ['IA-5', 'IA-2', 'IA-6'],
                    'hipaa': ['164.312(d)'],
                    'pci_dss_4': ['2.1', '8.3'],
                    'soc2': ['CC6.1'],
                    'SOC1-Type2': ['ITGC-03'],
                    'iso_27001_2022': ['5.17', '8.5']
                }
            ),
            'insecure_configuration': ControlMapping(
                'insecure_configuration',
                'Insecure system or service configuration',
                {
                    'nist_800_53': ['CM-2', 'CM-6', 'CM-3', 'SA-8'],
                    'hipaa': ['164.312(a)(1)'],
                    'pci_dss_4': ['2.1', '2.2'],
                    'soc2': ['CC6.1'],
                    'iso_27001_2022': ['8.9']
                }
            ),
            'missing_logging': ControlMapping(
                'missing_logging',
                'Insufficient logging or monitoring',
                {
                    'nist_800_53': ['AU-2', 'AU-3', 'AU-6', 'AU-8', 'AU-12', 'SI-4'],
                    'hipaa': ['164.312(b)'],
                    'pci_dss_4': ['10.1', '10.2'],
                    'soc2': ['CC7.2'],
                    'iso_27001_2022': ['8.15', '8.16']
                }
            ),
            'information_disclosure': ControlMapping(
                'information_disclosure',
                'Sensitive information disclosure',
                {
                    'nist_800_53': ['AC-3', 'SC-8', 'SC-28', 'SI-12'],
                    'hipaa': ['164.312(e)(1)', '164.312(c)(1)'],
                    'pci_dss_4': ['3.1', '3.5'],
                    'soc2': ['CC6.7'],
                    'iso_27001_2022': ['8.24']
                }
            ),
            'network_service': ControlMapping(
                'network_service',
                'Network service discovery',
                {
                    'nist_800_53': ['CM-7', 'CM-8', 'SC-7', 'AC-4', 'AC-17'],
                    'pci_dss_4': ['1.1', '1.2'],
                    'soc2': ['CC6.6'],
                    'iso_27001_2022': ['8.20', '8.21']
                }
            ),
            'host_discovery': ControlMapping(
                'host_discovery',
                'Host/asset discovery',
                {
                    'nist_800_53': ['CM-8', 'PM-5'],
                    'pci_dss_4': ['1.1'],
                    'soc2': ['CC6.1'],
                    'iso_27001_2022': ['8.1']
                }
            ),
            # Windows scanner finding types
            'endpoint_protection': ControlMapping(
                'endpoint_protection',
                'Endpoint protection/antivirus configuration',
                {
                    'nist_800_53': ['SI-3', 'SI-4', 'SI-8', 'SC-44'],
                    'hipaa': ['164.308(a)(1)'],
                    'pci_dss_4': ['5.1', '5.2'],
                    'soc2': ['CC6.8'],
                    'iso_27001_2022': ['8.7']
                }
            ),
            'disk_encryption': ControlMapping(
                'disk_encryption',
                'Disk/volume encryption status',
                {
                    'nist_800_53': ['SC-28', 'MP-5'],
                    'hipaa': ['164.312(a)(1)'],
                    'pci_dss_4': ['3.5'],
                    'soc2': ['CC6.7'],
                    'iso_27001_2022': ['8.24']
                }
            ),
            'firewall_config': ControlMapping(
                'firewall_config',
                'Host firewall configuration',
                {
                    'nist_800_53': ['SC-7', 'AC-4', 'SC-5'],
                    'pci_dss_4': ['1.1', '1.2'],
                    'soc2': ['CC6.6'],
                    'iso_27001_2022': ['8.20']
                }
            ),
            'audit_policy': ControlMapping(
                'audit_policy',
                'System audit/logging policy',
                {
                    'nist_800_53': ['AU-2', 'AU-3', 'AU-6', 'AU-8', 'AU-9', 'AU-12'],
                    'hipaa': ['164.312(b)'],
                    'pci_dss_4': ['10.1', '10.2'],
                    'soc2': ['CC7.2'],
                    'iso_27001_2022': ['8.15']
                }
            ),
            'password_policy': ControlMapping(
                'password_policy',
                'Password/credential policy',
                {
                    'nist_800_53': ['IA-5', 'IA-2', 'IA-4', 'IA-6'],
                    'hipaa': ['164.312(d)'],
                    'pci_dss_4': ['8.3'],
                    'soc2': ['CC6.1'],
                    'iso_27001_2022': ['5.17', '8.5']
                }
            ),
            'account_hygiene': ControlMapping(
                'account_hygiene',
                'User account management and hygiene',
                {
                    'nist_800_53': ['AC-2', 'AC-5', 'AC-6', 'AC-11', 'AC-17'],
                    'pci_dss_4': ['8.1', '7.1'],
                    'soc2': ['CC6.1'],
                    'iso_27001_2022': ['5.15']
                }
            ),
            'scheduled_task_anomaly': ControlMapping(
                'scheduled_task_anomaly',
                'Suspicious scheduled task or service',
                {
                    'nist_800_53': ['CM-7', 'SI-4'],
                    'soc2': ['CC7.1'],
                    'iso_27001_2022': ['8.16']
                }
            ),
            'privilege_escalation': ControlMapping(
                'privilege_escalation',
                'Privilege escalation risk',
                {
                    'nist_800_53': ['AC-6'],
                    'pci_dss_4': ['7.1'],
                    'soc2': ['CC6.3'],
                    'iso_27001_2022': ['5.15', '8.2']
                }
            ),
            'unauthorized_ai': ControlMapping(
                'unauthorized_ai',
                'Unauthorized AI installation or usage',
                {
                    'nist_800_53': ['CM-7', 'CM-10', 'CM-11', 'SA-9', 'SI-4', 'AC-20'],
                    'pci_dss_4': ['6.3', '12.8'],
                    'hipaa': ['164.308(a)(1)', '164.312(a)(1)', '164.312(e)(1)'],
                    'soc2': ['CC6.7', 'CC6.8', 'CC7.1'],
                    'iso_27001_2022': ['5.23', '8.9', '8.12', '8.20']
                }
            ),
        }

    # ----------------------------------------------------------------
    # Category-to-finding-type mapping.
    # Scanner findings carry a 'category' field (e.g., "network", "web",
    # "auth").  This maps each category to the finding_type keys that are
    # relevant for that category, enabling broader control coverage when
    # an explicit finding_type is absent.
    # ----------------------------------------------------------------
    _CATEGORY_FINDING_MAP: Dict[str, List[str]] = {
        'network': [
            'open_port', 'network_service', 'firewall_config', 'ssl_vulnerability',
            'weak_cipher', 'expired_certificate', 'host_discovery',
        ],
        'web': [
            'web_vulnerability', 'ssl_vulnerability', 'information_disclosure',
            'insecure_configuration', 'expired_certificate',
        ],
        'auth': [
            'authentication_weakness', 'default_credentials', 'password_policy',
            'account_hygiene',
        ],
        'authentication': [
            'authentication_weakness', 'default_credentials', 'password_policy',
            'account_hygiene',
        ],
        'encryption': [
            'ssl_vulnerability', 'weak_cipher', 'expired_certificate',
            'disk_encryption',
        ],
        'crypto': [
            'ssl_vulnerability', 'weak_cipher', 'expired_certificate',
            'disk_encryption',
        ],
        'configuration': [
            'insecure_configuration', 'firewall_config', 'audit_policy',
        ],
        'endpoint': [
            'endpoint_protection', 'disk_encryption', 'insecure_configuration',
            'firewall_config',
        ],
        'vulnerability': [
            'cve_vulnerability', 'web_vulnerability', 'missing_patch',
        ],
        'patching': [
            'missing_patch', 'cve_vulnerability',
        ],
        'logging': [
            'missing_logging', 'audit_policy',
        ],
        'access': [
            'account_hygiene', 'privilege_escalation', 'authentication_weakness',
        ],
        'malware': [
            'endpoint_protection',
        ],
        'compliance': [
            'insecure_configuration', 'audit_policy', 'missing_logging',
        ],
        'windows': [
            'endpoint_protection', 'insecure_configuration',
        ],
    }

    # ----------------------------------------------------------------
    # Scanner-type to additional finding types.  Findings from certain
    # scanners automatically get extra finding-type associations.
    # ----------------------------------------------------------------
    _SCANNER_FINDING_MAP: Dict[str, List[str]] = {
        'network_scanner': [
            'open_port', 'network_service', 'host_discovery',
        ],
        'web_scanner': [
            'web_vulnerability', 'ssl_vulnerability',
        ],
        'windows_scanner': [
            'endpoint_protection', 'insecure_configuration',
        ],
        'vulnerability_scanner': [
            'cve_vulnerability', 'missing_patch',
        ],
    }

    # ----------------------------------------------------------------
    # Cross-framework control domain keywords.
    # Maps security concept keywords to lists of (framework, control_id)
    # tuples.  These extend the hardcoded ControlMappings to cover YAML-
    # loaded frameworks (CMMC, CIS, GDPR, FEDRAMP, NIS2, SOX, etc.).
    # Built once in __init__ via _build_control_keyword_index().
    # ----------------------------------------------------------------

    # Keyword-to-finding-type map for intelligent secondary matching.
    # Each entry: keyword (lowercased) -> list of finding_type keys that apply.
    _KEYWORD_FINDING_MAP: Dict[str, List[str]] = {
        # Network / firewall
        'firewall': ['firewall_config', 'open_port'],
        'iptables': ['firewall_config'],
        'nftables': ['firewall_config'],
        'network boundary': ['firewall_config', 'network_service'],
        'open port': ['open_port', 'network_service'],
        'listening port': ['open_port', 'network_service'],
        'smb': ['open_port', 'network_service', 'insecure_configuration'],
        'rdp': ['open_port', 'network_service', 'authentication_weakness'],
        'telnet': ['open_port', 'insecure_configuration'],
        'ftp': ['open_port', 'insecure_configuration'],
        'ssh': ['open_port', 'network_service'],
        'port': ['open_port', 'network_service'],
        # Authentication / passwords
        'password': ['password_policy', 'authentication_weakness'],
        'credential': ['default_credentials', 'authentication_weakness'],
        'default credential': ['default_credentials'],
        'weak password': ['password_policy', 'authentication_weakness'],
        'authentication': ['authentication_weakness'],
        'mfa': ['authentication_weakness'],
        'multi-factor': ['authentication_weakness'],
        'brute force': ['authentication_weakness', 'password_policy'],
        'lockout': ['password_policy', 'authentication_weakness'],
        # Encryption / SSL / TLS
        'ssl': ['ssl_vulnerability'],
        'tls': ['ssl_vulnerability'],
        'certificate': ['expired_certificate', 'ssl_vulnerability'],
        'cipher': ['weak_cipher'],
        'encryption': ['ssl_vulnerability', 'disk_encryption'],
        'bitlocker': ['disk_encryption'],
        'cryptograph': ['weak_cipher', 'ssl_vulnerability'],
        # Logging / audit
        'logging': ['missing_logging', 'audit_policy'],
        'log': ['missing_logging', 'audit_policy'],
        'audit': ['audit_policy', 'missing_logging'],
        'siem': ['missing_logging'],
        'monitoring': ['missing_logging'],
        'event log': ['missing_logging', 'audit_policy'],
        # Vulnerabilities / patching
        'cve': ['cve_vulnerability'],
        'vulnerability': ['cve_vulnerability', 'web_vulnerability'],
        'patch': ['missing_patch'],
        'update': ['missing_patch'],
        'outdated': ['missing_patch'],
        'eol': ['missing_patch'],
        'end of life': ['missing_patch'],
        # Configuration
        'configuration': ['insecure_configuration'],
        'misconfiguration': ['insecure_configuration'],
        'hardening': ['insecure_configuration'],
        'baseline': ['insecure_configuration'],
        'cis benchmark': ['insecure_configuration'],
        # Access control / accounts
        'privilege': ['privilege_escalation', 'account_hygiene'],
        'admin': ['privilege_escalation', 'account_hygiene'],
        'administrator': ['privilege_escalation', 'account_hygiene'],
        'least privilege': ['privilege_escalation'],
        'account': ['account_hygiene'],
        'user account': ['account_hygiene'],
        'stale account': ['account_hygiene'],
        'inactive account': ['account_hygiene'],
        'access control': ['account_hygiene', 'insecure_configuration'],
        # Endpoint / malware
        'antivirus': ['endpoint_protection'],
        'anti-virus': ['endpoint_protection'],
        'malware': ['endpoint_protection'],
        'endpoint protection': ['endpoint_protection'],
        'windows defender': ['endpoint_protection'],
        'edr': ['endpoint_protection'],
        # Information disclosure
        'information disclosure': ['information_disclosure'],
        'sensitive data': ['information_disclosure'],
        'data exposure': ['information_disclosure'],
        'data leak': ['information_disclosure'],
        # Web
        'xss': ['web_vulnerability'],
        'sql injection': ['web_vulnerability'],
        'sqli': ['web_vulnerability'],
        'csrf': ['web_vulnerability'],
        'injection': ['web_vulnerability'],
        'web application': ['web_vulnerability'],
        # Scheduled tasks / services
        'scheduled task': ['scheduled_task_anomaly'],
        'cron': ['scheduled_task_anomaly'],
        'service': ['network_service'],
        # AI
        'unauthorized ai': ['unauthorized_ai'],
        'ai tool': ['unauthorized_ai'],
        'copilot': ['unauthorized_ai'],
        'chatgpt': ['unauthorized_ai'],
    }

    # ----------------------------------------------------------------
    # Security concept keywords for control-description matching.
    # Maps broad security concepts to search terms used to match against
    # YAML-loaded control names and descriptions.
    # ----------------------------------------------------------------
    _CONCEPT_KEYWORDS: Dict[str, List[str]] = {
        'ssl_vulnerability': [
            'cryptograph', 'encrypt', 'ssl', 'tls', 'transmission', 'transit',
            'certificate', 'cipher', 'crypto',
        ],
        'weak_cipher': [
            'cryptograph', 'cipher', 'encrypt', 'crypto', 'key management',
        ],
        'expired_certificate': [
            'certificate', 'cryptograph', 'key management', 'encrypt',
        ],
        'open_port': [
            'network', 'firewall', 'boundary', 'port', 'perimeter',
            'segment', 'traffic', 'communic',
        ],
        'network_service': [
            'network', 'service', 'boundary', 'perimeter', 'segment',
            'traffic', 'communic', 'infrastructure',
        ],
        'cve_vulnerability': [
            'vulnerabilit', 'patch', 'flaw', 'remediat', 'scan',
            'risk assess', 'threat', 'update',
        ],
        'missing_patch': [
            'patch', 'update', 'remediat', 'flaw', 'vulnerabilit',
            'maintenance', 'configuration management',
        ],
        'web_vulnerability': [
            'web', 'application', 'vulnerabilit', 'injection', 'cross-site',
            'input validat', 'software', 'code review',
        ],
        'authentication_weakness': [
            'authenticat', 'identif', 'credential', 'password', 'logon',
            'login', 'mfa', 'multi-factor', 'access control',
        ],
        'default_credentials': [
            'credential', 'password', 'default', 'authenticat', 'identif',
        ],
        'insecure_configuration': [
            'configur', 'harden', 'baseline', 'secure setting', 'system setting',
            'security setting',
        ],
        'missing_logging': [
            'log', 'audit', 'monitor', 'event', 'detect', 'alert',
            'record', 'siem',
        ],
        'information_disclosure': [
            'data protect', 'confidential', 'sensitive', 'privacy',
            'disclosure', 'data loss', 'classif',
        ],
        'endpoint_protection': [
            'malware', 'anti-virus', 'antivirus', 'malicious', 'endpoint',
            'threat detect', 'protection',
        ],
        'disk_encryption': [
            'encrypt', 'data at rest', 'storage protect', 'media protect',
            'cryptograph',
        ],
        'firewall_config': [
            'firewall', 'network security', 'boundary', 'perimeter',
            'filter', 'segment',
        ],
        'audit_policy': [
            'audit', 'log', 'accountab', 'record', 'event', 'monitor',
        ],
        'password_policy': [
            'password', 'credential', 'authenticat', 'identif',
        ],
        'account_hygiene': [
            'account', 'user', 'access control', 'provision', 'privilege',
            'role', 'authoriz',
        ],
        'privilege_escalation': [
            'privilege', 'least privilege', 'access control', 'authoriz',
            'role', 'segregat', 'separat',
        ],
        'host_discovery': [
            'asset', 'inventory', 'discover', 'identif',
        ],
        'scheduled_task_anomaly': [
            'monitor', 'detect', 'anomal', 'schedul', 'integrity',
        ],
        'unauthorized_ai': [
            'software', 'unauthoriz', 'install', 'approved', 'shadow',
            'third party', 'vendor',
        ],
    }

    def _build_control_keyword_index(self):
        """Build a reverse index from (framework, control_id) to matching concepts.

        For every YAML-loaded control, checks its name/description/family
        against _CONCEPT_KEYWORDS to pre-compute which finding types each
        control is relevant for.  This extends the hardcoded ControlMappings
        to cover all YAML-loaded frameworks.

        Requires at least 2 keyword hits per concept to reduce false positives.
        """
        # concept_to_controls: finding_type -> {framework -> set(control_ids)}
        self._concept_controls: Dict[str, Dict[str, Set[str]]] = {}

        # Frameworks already fully covered by hardcoded mappings — skip them
        # to avoid duplication of those curated, high-quality mappings.
        hardcoded_fws = {'nist_800_53', 'hipaa', 'pci_dss_4', 'soc2', 'SOC1-Type2', 'iso_27001_2022'}

        for fw_id, fw_controls in self.controls.items():
            if fw_id in hardcoded_fws:
                continue
            for ctrl_id, ctrl in fw_controls.items():
                # Build searchable text from control metadata
                search_text = f"{ctrl.control_name} {ctrl.description} {ctrl.family}".lower()
                for concept, keywords in self._CONCEPT_KEYWORDS.items():
                    # Count how many distinct keywords from this concept hit
                    hits = sum(1 for kw in keywords if kw in search_text)
                    # Require 2+ keyword hits to reduce false positives
                    if hits >= 2:
                        by_fw = self._concept_controls.setdefault(concept, {})
                        by_fw.setdefault(fw_id, set()).add(ctrl_id)

    def infer_finding_types(self, title: str, description: str = '') -> Set[str]:
        """Infer applicable finding_type keys from a finding's title and description.

        Uses keyword matching against ``_KEYWORD_FINDING_MAP`` to broaden
        control coverage beyond the explicit ``finding_type`` assigned by the
        scanner.  Returns a set of finding_type strings.
        """
        text = f"{title} {description}".lower()
        matched: Set[str] = set()
        for keyword, types in self._KEYWORD_FINDING_MAP.items():
            if keyword in text:
                matched.update(types)
        return matched

    def map_finding(self, finding: dict) -> List[dict]:
        """Map a finding dict to all relevant compliance controls.

        This is the primary entry point for mapping scanner findings to
        compliance framework controls.  It combines:
        1. Explicit finding_type matching (from ``self.mappings``)
        2. Keyword-based inference from title/description
        3. Category-based matching (finding category -> finding types)
        4. Scanner-specific associations (scanner name -> finding types)
        5. Control-description matching against YAML-loaded frameworks

        Args:
            finding: Dict with keys such as 'title', 'category', 'severity',
                     'scanner', 'finding_type', 'description'.

        Returns:
            List of dicts, each with keys: framework, control_id, control_name,
            family, match_source (how it was matched).
        """
        title = finding.get('title', '')
        description = finding.get('description', '')
        category = finding.get('category', '').lower()
        scanner = finding.get('scanner', '').lower()
        finding_type = finding.get('finding_type', '')
        frameworks_filter = finding.get('frameworks', None)

        # --- Phase 1: Collect all relevant finding types ---
        types_to_check: Set[str] = set()

        # 1a. Explicit finding_type
        if finding_type and finding_type in self.mappings:
            types_to_check.add(finding_type)

        # 1b. Keyword inference from title/description
        if title or description:
            types_to_check.update(self.infer_finding_types(title, description))

        # 1c. Category-based matching
        if category:
            cat_types = self._CATEGORY_FINDING_MAP.get(category, [])
            types_to_check.update(cat_types)

        # 1d. Scanner-specific associations
        if scanner:
            scanner_types = self._SCANNER_FINDING_MAP.get(scanner, [])
            types_to_check.update(scanner_types)

        # --- Phase 2: Gather controls from hardcoded mappings ---
        # Track (fw, ctrl_id) to avoid duplicates
        seen: Set[tuple] = set()
        results: List[dict] = []

        for ft in types_to_check:
            mapping = self.mappings.get(ft)
            if not mapping:
                continue
            for fw, ctrl_ids in mapping.controls.items():
                if frameworks_filter and fw not in frameworks_filter:
                    continue
                for cid in ctrl_ids:
                    key = (fw, cid)
                    if key in seen:
                        continue
                    seen.add(key)
                    ctrl = self.get_control(fw, cid)
                    results.append({
                        'framework': fw,
                        'control_id': cid,
                        'control_name': ctrl.control_name if ctrl else cid,
                        'family': ctrl.family if ctrl else '',
                        'match_source': 'mapping',
                    })

        # --- Phase 3: Gather controls from YAML keyword index ---
        for ft in types_to_check:
            concept_map = self._concept_controls.get(ft, {})
            for fw, ctrl_ids in concept_map.items():
                if frameworks_filter and fw not in frameworks_filter:
                    continue
                for cid in ctrl_ids:
                    key = (fw, cid)
                    if key in seen:
                        continue
                    seen.add(key)
                    ctrl = self.get_control(fw, cid)
                    results.append({
                        'framework': fw,
                        'control_id': cid,
                        'control_name': ctrl.control_name if ctrl else cid,
                        'family': ctrl.family if ctrl else '',
                        'match_source': 'keyword_index',
                    })

        # --- Phase 4: Universal NIST controls for any scanner finding ---
        nist_key = 'nist_800_53'
        if not frameworks_filter or nist_key in frameworks_filter:
            for cid in self._UNIVERSAL_NIST_CONTROLS:
                key = (nist_key, cid)
                if key not in seen:
                    seen.add(key)
                    ctrl = self.get_control(nist_key, cid)
                    results.append({
                        'framework': nist_key,
                        'control_id': cid,
                        'control_name': ctrl.control_name if ctrl else cid,
                        'family': ctrl.family if ctrl else '',
                        'match_source': 'universal',
                    })

        return results

    def get_controls_for_finding(self, finding_type: str, frameworks: List[str] = None,
                                  title: str = '', description: str = '') -> Dict[str, List[str]]:
        """Get applicable controls for a finding type, enhanced with keyword inference.

        If *title* or *description* are provided, keyword-based inference is
        used to discover additional matching finding types beyond the explicit
        ``finding_type``, broadening control coverage without false positives.
        """
        # Start with the explicit finding_type
        types_to_check: Set[str] = set()
        if finding_type in self.mappings:
            types_to_check.add(finding_type)

        # Augment with keyword-inferred types
        if title or description:
            types_to_check.update(self.infer_finding_types(title, description))

        if not types_to_check:
            return {}

        # Merge controls from all matched types (hardcoded mappings)
        merged: Dict[str, List[str]] = {}
        for ft in types_to_check:
            mapping = self.mappings.get(ft)
            if not mapping:
                continue
            for fw, ctrls in mapping.controls.items():
                if frameworks and fw not in frameworks:
                    continue
                existing = merged.setdefault(fw, [])
                for c in ctrls:
                    if c not in existing:
                        existing.append(c)

        # Merge controls from YAML keyword index (broader framework coverage)
        for ft in types_to_check:
            concept_map = self._concept_controls.get(ft, {})
            for fw, ctrl_ids in concept_map.items():
                if frameworks and fw not in frameworks:
                    continue
                existing = merged.setdefault(fw, [])
                for c in ctrl_ids:
                    if c not in existing:
                        existing.append(c)

        # Append universal NIST controls that every scan finding supports
        nist_key = 'nist_800_53'
        if not frameworks or nist_key in frameworks:
            existing = merged.setdefault(nist_key, [])
            for c in self._UNIVERSAL_NIST_CONTROLS:
                if c not in existing:
                    existing.append(c)

        return merged

    def get_all_controls_for_framework(self, framework: str) -> Dict[str, Control]:
        """Get all controls for a specific framework."""
        return self.controls.get(framework, {})

    def get_control(self, framework: str, control_id: str) -> Optional[Control]:
        """Get a specific control."""
        framework_controls = self.controls.get(framework, {})
        return framework_controls.get(control_id)

    def get_supported_frameworks(self) -> List[str]:
        """Get list of supported frameworks."""
        return list(self.controls.keys())

    def map_finding_to_controls(self, finding_type: str, evidence_id: str,
                                 evidence_manager, frameworks: List[str] = None,
                                 title: str = '', description: str = ''):
        """Map a finding's evidence to all applicable controls.

        Uses both the explicit *finding_type* and keyword inference from
        *title*/*description* to maximise control coverage.
        """
        controls = self.get_controls_for_finding(
            finding_type, frameworks, title=title, description=description
        )

        for framework, control_ids in controls.items():
            for control_id in control_ids:
                control = self.get_control(framework, control_id)
                if control:
                    evidence_manager.map_to_control(
                        evidence_id,
                        framework,
                        control_id,
                        control.control_name,
                        control.family
                    )

    def get_attestation_types_for_control(self, framework: str,
                                           control_id: str) -> List[str]:
        """Return which attestation types satisfy a given control.

        Searches the ATTESTATION_TYPES taxonomy for entries that map
        to this (framework, control_id) pair.
        """
        result = []
        for att_type, info in ATTESTATION_TYPES.items():
            fw_controls = info.get('controls', {}).get(framework, [])
            if control_id in fw_controls:
                result.append(att_type)
        return result

    def get_controls_for_attestation_type(self, attestation_type: str,
                                            framework: str = None) -> Dict[str, List[str]]:
        """Return all controls satisfied by an attestation type.

        Parameters
        ----------
        attestation_type : str
            Key from ATTESTATION_TYPES.
        framework : str, optional
            If given, restrict to this framework only.

        Returns
        -------
        Dict mapping framework_id -> list of control_ids.
        """
        info = ATTESTATION_TYPES.get(attestation_type, {})
        controls = info.get('controls', {})
        if framework:
            subset = controls.get(framework, [])
            return {framework: subset} if subset else {}
        return dict(controls)

    def generate_compliance_summary(self, evidence_manager, framework: str) -> Dict:
        """Generate compliance summary for a framework."""
        all_controls = self.get_all_controls_for_framework(framework)
        summary = {
            'framework': framework,
            'total_controls': len(all_controls),
            'controls_with_evidence': 0,
            'controls_without_evidence': 0,
            'control_details': []
        }

        for control_id, control in all_controls.items():
            evidence = evidence_manager.get_evidence_for_control(framework, control_id)
            has_evidence = len(evidence) > 0

            if has_evidence:
                summary['controls_with_evidence'] += 1
            else:
                summary['controls_without_evidence'] += 1

            summary['control_details'].append({
                'control_id': control_id,
                'control_name': control.control_name,
                'family': control.family,
                'evidence_count': len(evidence),
                'has_evidence': has_evidence
            })

        return summary


# Singleton
_mapper: Optional[ComplianceMapper] = None


def get_compliance_mapper() -> ComplianceMapper:
    """Get the compliance mapper singleton."""
    global _mapper
    if _mapper is None:
        _mapper = ComplianceMapper()
    return _mapper


if __name__ == '__main__':
    mapper = get_compliance_mapper()
    print(f"Supported frameworks: {mapper.get_supported_frameworks()}")
    print(f"\nControls for 'cve_vulnerability':")
    for fw, controls in mapper.get_controls_for_finding('cve_vulnerability').items():
        print(f"  {fw}: {controls}")
