"""Command-line interface for audit tool."""
import sys
from pathlib import Path
import argparse
from rich.console import Console
from rich.table import Table
from rich import box

from audit.config import Config
from audit.metrics import calculate_metrics
from audit.evidence import write_report
from audit.export import export_pdf, export_json
from audit.policy_profiles import get_profile, PolicyProfile
from audit.metric_contract import MetricSeverity, AuditMode, TaskType
from audit.enforcement import (
    EnforcementAnalyzer, EnforcementLayer, AuditStatus, MetricEnforcement
)
from typing import Optional, Dict, Any
import os
import json
import requests
import pandas as pd


console = Console()


def _flatten_category_metrics(category_metrics: Dict[str, Any], flat_metrics: Dict[str, float], prefix: str = ""):
    """Flatten category-based metrics structure for threshold checking."""
    for key, value in category_metrics.items():
        full_key = f"{prefix}_{key}" if prefix else key
        
        if isinstance(value, dict):
            _flatten_category_metrics(value, flat_metrics, full_key)
        elif isinstance(value, (int, float)):
            flat_metrics[full_key] = float(value)
        elif isinstance(value, list):
            # Handle worst_case_slices and other lists
            if key == "worst_case_slices":
                for i, slice_data in enumerate(value):
                    if isinstance(slice_data, dict) and "value" in slice_data:
                        flat_metrics[f"{full_key}_{i}_value"] = float(slice_data["value"])


def _flatten_api_metrics(api_results: Dict[str, Any], flat_metrics: Dict[str, float]):
    """Flatten API audit metrics for threshold checking."""
    for category, data in api_results.get("api_categories", {}).items():
        metrics = data.get("metrics", {})
        for metric_name, value in metrics.items():
            if isinstance(value, (int, float)):
                flat_metrics[f"{category}_{metric_name}"] = float(value)


def run_audit(
    config_path: str,
    fail_on_threshold: bool = True,
    output_dir: Optional[str] = None,
    upload: bool = False
) -> bool:
    """
    Run audit and return True if passed, False if failed.
    
    NOTE: Local audits are for developer validation only. They do NOT provide:
    - Audit history or trend analysis
    - Comparison against past runs
    - Regulator-ready summaries
    - Signed evidence for compliance
    - CI/CD enforcement capabilities
    
    For governance enforcement, compliance evidence, and regulatory reporting,
    audits must be uploaded to Verity's platform.
    """
    # Show license notice for local audits (when no API key)
    api_key = os.getenv("VERITY_API_KEY")
    if not api_key and not upload:
        console.print("\n[yellow]⚠️  LOCAL AUDIT MODE[/yellow]")
        console.print("[dim]Local audits are for developer validation only.[/dim]")
        console.print("[dim]For governance enforcement and compliance evidence, upload to Verity's platform.[/dim]\n")
    
    # Load configuration
    try:
        config = Config(config_path)
    except Exception as e:
        console.print(f"[red]Error loading config: {e}[/red]")
        return False
    
    # Handle API model mode differently
    if config.mode == 'api_model':
        console.print("[cyan]API Model Audit Mode[/cyan]")
        console.print("[yellow]⚠️  API model audits require platform upload for full functionality.[/yellow]")
        console.print("[yellow]   Local API audits are not yet supported in CLI mode.[/yellow]")
        console.print("[yellow]   Please use the Verity platform for API model audits.[/yellow]")
        console.print("[yellow]   For CI/CD, ensure your workflow uploads results to Verity.[/yellow]")
        return True  # Don't fail, just inform user
    
    # Warn if dataset metadata is missing (required for production)
    if not config.dataset_metadata:
        console.print("[yellow]⚠️  Warning: dataset_metadata not provided in config[/yellow]")
        console.print("[yellow]   For production audits, include: description, population, jurisdiction, sample_size[/yellow]")
    
    # For hybrid mode, we still need dataset for the dataset portion
    if config.mode == 'hybrid' and not config.dataset:
        console.print("[red]Error: Hybrid mode requires a dataset for the dataset_model portion[/red]")
        return False
    
    # Handle hybrid mode
    if config.mode == 'hybrid':
        console.print("[cyan]Hybrid Audit Mode (Dataset + API + Integration Tests)[/cyan]")
        
        try:
            from audit.hybrid_audit import run_hybrid_audit
            
            # Get test suite path
            test_suite_path = Path(config.test_suite_source) if config.test_suite_source else None
            if not test_suite_path or not test_suite_path.exists():
                console.print("[yellow]⚠️  Test suite not found for API component. Creating starter suite...[/yellow]")
                from audit.api_test_suite import create_starter_test_suite
                starter_suite = create_starter_test_suite()
                test_suite_path = Path.cwd() / "tests" / "verity_starter_suite.yaml"
                starter_suite.save_to_file(test_suite_path)
                console.print(f"[green]Created starter test suite at: {test_suite_path}[/green]")
            
            console.print("[cyan]Running hybrid audit...[/cyan]")
            
            hybrid_results = run_hybrid_audit(
                dataset_path=str(config.dataset),
                model_path=str(config.model),
                label_col=config.label,
                sensitive_attrs=config.sensitive,
                api_provider=config.api_provider,
                api_model=config.api_model,
                api_key_env=config.api_key_env,
                test_suite_path=test_suite_path,
                task_type=config.task_type,
                positive_class=config.positive_class,
                domain_profile=config.domain_profile
            )
            
            # Combine dataset and API metrics
            category_metrics = {
                **hybrid_results["dataset_component"],
                "api_categories": hybrid_results.get("api_component", {}),
                "end_to_end_integration": hybrid_results.get("end_to_end_integration", {})
            }
            
            # Flatten for threshold checking
            metrics = {}
            _flatten_category_metrics(hybrid_results["dataset_component"], metrics)
            if hybrid_results.get("api_component"):
                _flatten_api_metrics({"api_categories": hybrid_results["api_component"]}, metrics)
            
            # Log summary
            console.print(f"[dim]Dataset audit: completed[/dim]")
            console.print(f"[dim]API audit: {'completed' if hybrid_results.get('api_component') else 'skipped'}[/dim]")
            console.print(f"[dim]Integration tests: completed[/dim]")
            
            # Integration test summary
            integration = hybrid_results.get("end_to_end_integration", {})
            if integration:
                console.print("\n[bold]Integration Test Results[/bold]")
                if "wrapper_leakage" in integration:
                    leakage = integration["wrapper_leakage"]
                    leakage_rate = leakage.get('leakage_rate', 0)
                    console.print(f"[dim]Wrapper leakage rate: {leakage_rate:.1%}[/dim]")
                if "demographic_consistency" in integration:
                    consistency = integration["demographic_consistency"]
                    consistency_rate = consistency.get('consistency_rate', 0)
                    console.print(f"[dim]Demographic consistency: {consistency_rate:.1%}[/dim]")
            
            # Get dataset info from dataset component
            dataset_info = {
                "shape": {
                    "rows": len(pd.read_csv(str(config.dataset))),
                    "columns": len(pd.read_csv(str(config.dataset)).columns)
                }
            }
            
            # Continue with threshold checking and report writing (shared logic below)
            # category_metrics and metrics are already set above
        except Exception as e:
            console.print(f"[red]Error running hybrid audit: {e}[/red]")
            import traceback
            console.print(f"[red]{traceback.format_exc()}[/red]")
            return False
    
    # Calculate metrics (only for dataset_model mode - hybrid already calculated above)
    if config.mode == 'dataset_model':
        if not config.dataset:
            console.print("[red]Error: Dataset is required for dataset_model and hybrid modes[/red]")
            return False
        if not config.model:
            console.print("[red]Error: Model is required for dataset_model and hybrid modes[/red]")
            return False
            
        try:
            console.print("[cyan]Calculating audit metrics (category-based)...[/cyan]")
            category_metrics, dataset_info = calculate_metrics(
                str(config.dataset),
                str(config.model),
                config.label,
                config.sensitive,
                task_type=config.task_type,
                positive_class=config.positive_class
            )
            
            # Flatten category metrics for threshold checking (backward compatibility)
            # Extract all metric values from categories
            metrics = {}
            _flatten_category_metrics(category_metrics, metrics)
            
            # Log dataset info (counts only, no raw data)
            console.print(f"[dim]Dataset: {dataset_info['shape']['rows']} rows × {dataset_info['shape']['columns']} columns[/dim]")
            if dataset_info.get('sensitive_attribute_distributions'):
                console.print("[dim]Sensitive attribute distributions logged[/dim]")
            
            # Log category summary
            fairness_attrs_count = len(category_metrics.get('fairness', {}).get('group_metrics', {}))
            console.print(f"[dim]Fairness metrics: {fairness_attrs_count} attributes analyzed[/dim]")
            
            # Warn if no protected attributes but fairness metrics are expected
            if fairness_attrs_count == 0 and config.sensitive:
                console.print(f"[yellow]⚠️  Warning: Protected columns specified ({config.sensitive}) but no fairness metrics computed.[/yellow]")
                console.print(f"[yellow]   This may indicate missing columns in dataset or insufficient group diversity.[/yellow]")
            elif fairness_attrs_count == 0 and not config.sensitive:
                console.print(f"[dim]Fairness metrics: Skipped (no protected attributes provided)[/dim]")
                console.print(f"[dim]   To enable fairness metrics, add 'protected_columns' to your verity.yml[/dim]")
            
            # Log feature alignment status
            if dataset_info.get('feature_alignment'):
                alignment = dataset_info['feature_alignment']
                if alignment.get('status') == 'aligned':
                    if alignment.get('extra_features'):
                        console.print(f"[dim]Feature alignment: PASS (expected {alignment.get('expected_features_count')}, found {alignment.get('found_features_count')})[/dim]")
                        console.print(f"[yellow]   Extra columns ignored: {', '.join(alignment.get('extra_features', [])[:5])}{'...' if len(alignment.get('extra_features', [])) > 5 else ''}[/yellow]")
                    else:
                        console.print(f"[dim]Feature alignment: PASS (expected {alignment.get('expected_features_count')}, found {alignment.get('found_features_count')})[/dim]")
                elif alignment.get('status') == 'no_expected_features':
                    console.print(f"[yellow]Feature alignment: WARNING (model does not specify expected features)[/yellow]")
            
            # Log artifact health
            if dataset_info.get('artifact_health'):
                health = dataset_info['artifact_health']
                if health.get('version_mismatch_risk') == 'medium':
                    console.print(f"[yellow]Artifact health: {health.get('serialization_format', 'unknown')} format, version mismatch risk: {health.get('version_mismatch_risk')}[/yellow]")
                    if health.get('recommended_action'):
                        console.print(f"[yellow]   {health.get('recommended_action')}[/yellow]")
                elif health.get('version_mismatch_risk') == 'high':
                    console.print(f"[red]Artifact health: {health.get('serialization_format', 'unknown')} format, version mismatch risk: {health.get('version_mismatch_risk')}[/red]")
                    if health.get('recommended_action'):
                        console.print(f"[red]   {health.get('recommended_action')}[/red]")
            
            console.print(f"[dim]Performance metrics: calculated[/dim]")
            console.print(f"[dim]Data quality checks: completed[/dim]")
            console.print(f"[dim]Explainability analysis: completed[/dim]")
        except Exception as e:
            console.print(f"[red]Error calculating metrics: {e}[/red]")
            import traceback
            console.print(f"[red]{traceback.format_exc()}[/red]")
            return False
    else:
        # Should not reach here, but handle gracefully
        console.print(f"[red]Error: Unknown audit mode: {config.mode}[/red]")
        return False
    
    # Determine plan from environment variable or API
    # IMPORTANT: Do this BEFORE threshold filtering so plan is correct
    plan = os.getenv("VERITY_PLAN", "starter").lower()  # Default to starter for local audits
    
    # If API key is available, try to detect plan from API BEFORE threshold filtering
    api_key = os.getenv("VERITY_API_KEY")
    if api_key:
        # Try to detect plan from API by making a lightweight request
        api_url = os.getenv("VERITY_API_URL") or "https://verityai-production.up.railway.app"
        try:
            # Make a lightweight request to detect plan (using a test endpoint or headers)
            import requests
            response = requests.get(
                f"{api_url}/v1/audits",
                headers={"X-API-Key": api_key},
                params={"limit": 1},
                timeout=3  # Short timeout to avoid blocking
            )
            # Check response headers for plan info
            detected_plan = response.headers.get("X-Organization-Plan")
            if detected_plan:
                plan = detected_plan.lower()
                console.print(f"[dim]Detected plan from API: {plan}[/dim]")
            elif response.status_code == 200:
                # If API key works (200 OK), assume Pro (Starter doesn't have API access)
                plan = "pro"
                console.print(f"[dim]API key detected: Assuming Pro plan (Starter plan has no API access)[/dim]")
            else:
                # If API key doesn't work, still assume Pro (since Starter has no API access)
                plan = "pro"
                console.print(f"[dim]API key detected: Assuming Pro plan (Starter plan has no API access)[/dim]")
        except (requests.exceptions.Timeout, requests.exceptions.ConnectionError):
            # If API is unreachable or times out, assume Pro based on API key presence
            plan = "pro"
            console.print(f"[dim]API key detected: Assuming Pro plan (Starter plan has no API access)[/dim]")
        except Exception as e:
            # If any other error, fall back to environment variable or default
            console.print(f"[dim]Could not detect plan from API: {e}. Using default: {plan}[/dim]")
        except Exception:
            # For any other error, assume Pro based on API key presence
            plan = "pro"
            console.print(f"[dim]API key detected: Assuming Pro plan (Starter plan has no API access)[/dim]")
    
    # Starter plan: Only check these 3 specific thresholds
    STARTER_THRESHOLDS = {
        "demographic_parity_diff": 0.10,
        "equal_opportunity_diff": 0.10,
        "disparate_impact_ratio_min": 0.80
    }
    
    # Filter thresholds based on plan
    if plan == "starter":
        # For starter, only use the 3 basic thresholds
        filtered_thresholds = {}
        for key in STARTER_THRESHOLDS.keys():
            if key in config.thresholds:
                filtered_thresholds[key] = config.thresholds[key]
            else:
                # Use default values if not in config
                filtered_thresholds[key] = STARTER_THRESHOLDS[key]
        # Also check for any threshold keys that match the starter pattern
        for key, value in config.thresholds.items():
            if any(starter_key in key.lower() for starter_key in ["demographic_parity_diff", "equal_opportunity_diff", "disparate_impact_ratio_min"]):
                filtered_thresholds[key] = value
        config.thresholds = filtered_thresholds
        console.print(f"[dim]Starter plan: Checking only basic fairness thresholds[/dim]")
    elif plan in ["pro", "enterprise"]:
        # Pro/Enterprise: Use all thresholds from config
        console.print(f"[dim]{plan.capitalize()} plan: Checking all configured thresholds + governance checks[/dim]")
    
    # Initialize enforcement analyzer
    has_protected_attrs = bool(config.sensitive and len(config.sensitive) > 0)
    enforcement_analyzer = EnforcementAnalyzer(
        dataset_info=dataset_info,
        has_protected_attributes=has_protected_attrs,
        task_type=config.task_type,
        domain_profile=getattr(config, 'domain_profile', None)
    )
    
    # Layer 0: Audit Validity Checks (Hard GATE)
    # These must pass before any other checks
    validity_checks = {}
    
    # Check dataset exists (implicit - we got here, so it exists)
    validity_checks['dataset_exists'] = MetricEnforcement(
        metric_id="dataset_exists",
        layer=EnforcementLayer.VALIDITY,
        value=True,
        threshold=True,
        passed=True,
        description="Dataset file exists"
    )
    
    # Check model loads (implicit - we got here, so it loads)
    validity_checks['model_loads'] = MetricEnforcement(
        metric_id="model_loads",
        layer=EnforcementLayer.VALIDITY,
        value=True,
        threshold=True,
        passed=True,
        description="Model loads successfully"
    )
    
    # Check label exists (implicit - we got here, so it exists)
    validity_checks['label_exists'] = MetricEnforcement(
        metric_id="label_exists",
        layer=EnforcementLayer.VALIDITY,
        value=True,
        threshold=True,
        passed=True,
        description="Label column exists in dataset"
    )
    
    # Check feature schema alignment with detailed reporting
    feature_alignment = dataset_info.get('feature_alignment', {})
    expected_count = feature_alignment.get('expected_features_count')
    found_count = feature_alignment.get('found_features_count')
    missing_features = feature_alignment.get('missing_features', [])
    extra_features = feature_alignment.get('extra_features', [])
    
    if feature_alignment.get('status') == 'aligned' and expected_count is not None:
        # Missing features → FAIL (Layer 0)
        schema_matches = len(missing_features) == 0
        
        if len(missing_features) > 0:
            validity_checks['feature_schema_matches'] = MetricEnforcement(
                metric_id="feature_schema_matches",
                layer=EnforcementLayer.VALIDITY,
                value=False,
                threshold=True,
                passed=False,
                description=f"Missing required features: {', '.join(missing_features[:5])}{'...' if len(missing_features) > 5 else ''}"
            )
        elif len(extra_features) > 0:
            # Only extra columns → PASS with info
            validity_checks['feature_schema_matches'] = MetricEnforcement(
                metric_id="feature_schema_matches",
                layer=EnforcementLayer.VALIDITY,
                value=True,
                threshold=True,
                passed=True,
                description=f"Schema matches. Extra columns ignored: {', '.join(extra_features[:5])}{'...' if len(extra_features) > 5 else ''}"
            )
        else:
            # Perfect match
            validity_checks['feature_schema_matches'] = MetricEnforcement(
                metric_id="feature_schema_matches",
                layer=EnforcementLayer.VALIDITY,
                value=True,
                threshold=True,
                passed=True,
                description="Feature schema matches model expectations"
            )
    else:
        # If model doesn't specify expected features, we can't validate alignment
        # This is a WARN, not a FAIL
        pass
    
    # Check dataset size with tiered minimums
    dataset_size_checks, is_size_failure = enforcement_analyzer.check_dataset_size()
    for check in dataset_size_checks:
        if check.layer == EnforcementLayer.VALIDITY:
            validity_checks[check.metric_id] = check
        elif check.layer == EnforcementLayer.RISK_SIGNALS:
            # Will be added to warn_metrics later
            pass
    
    # Check thresholds (using flattened metrics)
    passed = True
    threshold_checks = {}
    
    # Compute data quality GATE metrics from category_metrics
    # These need special handling because they're nested in data_quality category
    data_quality = category_metrics.get('data_quality', {})
    
    # Extract missing value rate max (for GATE - Layer 1)
    missingness = data_quality.get('missingness', {})
    if missingness:
        max_missing_rate = max(
            (v.get('percentage', 0) if isinstance(v, dict) else 0)
            for v in missingness.values()
        ) if missingness else 0.0
        metrics['data_quality_missing_value_rate_max'] = max_missing_rate
    
    # Add duplicate row rate check (for GATE - Layer 1)
    duplicate_rows = data_quality.get('duplicate_rows', {})
    if duplicate_rows:
        duplicate_rate = duplicate_rows.get('rate', 0.0)
        if duplicate_rate > 0:
            metrics['data_quality_duplicate_row_rate_max'] = duplicate_rate
    
    # Add constant column check (WARN only - stored for reporting)
    constant_columns = data_quality.get('constant_columns', [])
    if constant_columns:
        metrics['data_quality_constant_columns'] = constant_columns
    
    # Add high-cardinality identifier check (WARN only - stored for reporting)
    high_cardinality = data_quality.get('high_cardinality_columns', [])
    if high_cardinality:
        metrics['data_quality_high_cardinality_columns'] = high_cardinality
    
    # Extract schema valid (for GATE - Layer 0) - schema is valid if dataset loaded successfully
    metrics['data_quality_schema_valid'] = 1.0  # True (1.0) if we got here, dataset loaded
    
    # Extract train_test_leakage (for GATE - Layer 1) - check if it exists in data_quality
    if 'train_test_leakage' in data_quality:
        leakage_value = data_quality.get('train_test_leakage', False)
        if isinstance(leakage_value, dict):
            leakage_detected = leakage_value.get('detected', False)
        else:
            leakage_detected = bool(leakage_value)
        metrics['train_test_leakage_detected'] = 1.0 if leakage_detected else 0.0
    else:
        # Default: no leakage detected (we don't have detection capability yet)
        metrics['train_test_leakage_detected'] = 0.0
    
    # Ensure performance metrics are available for gating (Layer 2 - mandatory)
    # For regression: require at least NRMSE or NMAE + R²
    # For classification: require accuracy
    # If no performance thresholds exist, add default ones
    if config.task_type == "regression":
        # Check for normalized performance metrics
        has_nrmse_threshold = any('nrmse' in k and k in config.thresholds for k in metrics.keys())
        has_nmae_threshold = any('nmae' in k and k in config.thresholds for k in metrics.keys())
        has_r2_threshold = any('r2' in k and k in config.thresholds for k in metrics.keys())
        
        # Find actual metric values - use nrmse_by_std as primary gate, nrmse_by_mean is report-only
        nrmse_std_metric = next((k for k in metrics.keys() if 'performance_overall_nrmse_by_std' in k), None)
        nrmse_mean_metric = next((k for k in metrics.keys() if 'performance_overall_nrmse_by_mean' in k), None)
        nmae_metric = next((k for k in metrics.keys() if 'performance_overall_nmae' in k), None)
        r2_metric = next((k for k in metrics.keys() if 'performance_overall_r2' in k), None)
        
        # Add default performance gates if missing - use nrmse_by_std as primary gate
        if not has_nrmse_threshold and nrmse_std_metric:
            # Default NRMSE by std threshold: 0.12 (12% error normalized by std)
            if nrmse_std_metric not in threshold_checks:
                nrmse_value = metrics.get(nrmse_std_metric, 0.0)
                threshold_checks[nrmse_std_metric] = {
                    "value": nrmse_value,
                    "threshold": 0.12,
                    "passed": nrmse_value <= 0.12
                }
        
        # Ensure nrmse_by_mean is report-only (not gated)
        if nrmse_mean_metric and nrmse_mean_metric not in threshold_checks:
            # Add to report metrics only
            pass
        
        if not has_r2_threshold and r2_metric:
            # Default R² threshold: 0.70 (70% explained variance)
            if r2_metric not in threshold_checks:
                r2_value = metrics.get(r2_metric, 0.0)
                threshold_checks[r2_metric] = {
                    "value": r2_value,
                    "threshold": 0.70,
                    "passed": r2_value >= 0.70
                }
    else:
        # Classification: check for accuracy threshold
        accuracy_metric = next((k for k in metrics.keys() if 'performance_overall_accuracy' in k), None)
        has_accuracy_threshold = accuracy_metric and any('accuracy' in k and k in config.thresholds for k in metrics.keys())
        
        if not has_accuracy_threshold and accuracy_metric:
            # Default accuracy threshold: 0.75 (75%)
            if accuracy_metric not in threshold_checks:
                accuracy_value = metrics.get(accuracy_metric, 0.0)
                threshold_checks[accuracy_metric] = {
                    "value": accuracy_value,
                    "threshold": 0.75,
                    "passed": accuracy_value >= 0.75
                }
    
    for metric_name, value in metrics.items():
        # EXCLUDE raw overall performance metrics - these are informational only
        # But ALLOW normalized metrics (nrmse, nmae, mape, smape) which should have thresholds
        if metric_name.startswith('performance_overall_'):
            # Allow normalized metrics to have thresholds
            if any(norm_metric in metric_name for norm_metric in ['nrmse', 'nmae', 'mape', 'smape']):
                pass  # Allow normalized metrics through
            else:
                # Skip raw metrics (mae, rmse, mean_error, r2) - these are informational only
                continue
        
        # EXCLUDE data quality schema metrics - these are informational only
        if metric_name.startswith('data_quality_schema_'):
            continue
        
        # EXCLUDE label distribution metrics - these are informational only
        if metric_name.startswith('data_quality_label_distribution_'):
            continue
        
        # EXCLUDE feature importance metrics - these are informational only
        if metric_name.startswith('explainability_feature_importance_'):
            continue
        
        # Try to find threshold using the new mapping system
        threshold_value, threshold_info = config.find_threshold_for_metric(metric_name)
        threshold_key = None
        
        # Extract numeric value from threshold_info if it's a dict
        if threshold_value is not None and isinstance(threshold_value, dict):
            threshold_value = threshold_value.get('value')
        if threshold_info is not None and isinstance(threshold_info, dict) and threshold_value is None:
            threshold_value = threshold_info.get('value')
        
        # If not found via mapping, try direct lookup and legacy matching
        if threshold_value is None:
            threshold_value_raw = config.get_threshold(metric_name)
            # Extract numeric value if it's a dict or tuple
            if isinstance(threshold_value_raw, tuple):
                threshold_value, threshold_info = threshold_value_raw
            else:
                threshold_value = threshold_value_raw
            # Extract numeric value from dict if needed
            if isinstance(threshold_value, dict):
                threshold_value = threshold_value.get('value')
            
            # If still not found, try legacy matching by base metric name
            if threshold_value is None:
                for base_name in config.thresholds.keys():
                    # Handle dict format thresholds
                    base_threshold = config.thresholds.get(base_name)
                    if isinstance(base_threshold, dict):
                        base_value = base_threshold.get('value')
                    else:
                        base_value = base_threshold
                    
                    if base_value is None:
                        continue
                    
                    # Check if metric name ends with the threshold key (with or without underscore prefix)
                    if metric_name.endswith(base_name) or metric_name.endswith(f"_{base_name}"):
                        threshold_value = base_value
                        threshold_key = base_name
                        threshold_info = base_threshold if isinstance(base_threshold, dict) else None
                        break
                    # Check if threshold key starts with a suffix of the metric name
                    # e.g., "disparate_impact_ratio_min" matches "gender_disparate_impact_ratio"
                    if '_' in metric_name:
                        # Try removing attribute prefix by finding where the threshold might start
                        # Check all possible suffixes
                        for i in range(1, len(metric_name.split('_'))):
                            suffix = '_'.join(metric_name.split('_')[i:])
                            if base_name.startswith(suffix) or suffix in base_name:
                                threshold_value = base_value
                                threshold_key = base_name
                                threshold_info = base_threshold if isinstance(base_threshold, dict) else None
                                break
                        if threshold_value is not None:
                            break
        
        if threshold_value is not None:
            # Ensure threshold_value is a number, not a dict
            if isinstance(threshold_value, dict):
                threshold_value = threshold_value.get('value')
            if threshold_value is None:
                continue  # Skip if we can't extract a value
            
            # Check threshold scope, unit, or metric name for type
            scope = threshold_info.get('scope') if threshold_info and isinstance(threshold_info, dict) else None
            unit = threshold_info.get('unit') if threshold_info and isinstance(threshold_info, dict) else None
            
            # Handle boolean thresholds (schema_valid, train_test_leakage_detected)
            if unit == "boolean" or "valid" in metric_name or "leakage_detected" in metric_name:
                # For boolean metrics: threshold True means we expect True (value must be True)
                # threshold False means we expect False (value must be False)
                if isinstance(threshold_value, bool):
                    threshold_bool = threshold_value
                else:
                    # Convert threshold to bool: 1.0/True = True, 0.0/False = False
                    threshold_bool = bool(threshold_value) if threshold_value is not None else True
                value_bool = bool(value) if value is not None else False
                metric_passed = value_bool == threshold_bool
                # Store original threshold_value for display
                threshold_display = threshold_value
            else:
                # Ensure threshold_value is numeric for numeric comparisons
                try:
                    threshold_value = float(threshold_value)
                    threshold_display = threshold_value
                except (ValueError, TypeError):
                    continue  # Skip if threshold_value is not numeric
                
                # Numeric thresholds
                is_ratio = "ratio" in metric_name or (threshold_key and "ratio" in threshold_key) or (scope and "ratio" in scope)
                is_diff = "diff" in metric_name or (threshold_key and "diff" in threshold_key) or (scope and "gap" in scope)
                is_min = "min" in metric_name or (threshold_key and "min" in threshold_key) or (scope and "min" in str(scope))
                
                if is_diff:
                    # For difference metrics, pass if value <= threshold
                    metric_passed = value <= threshold_value
                elif is_ratio or is_min:
                    # For ratio/min metrics, pass if value >= threshold
                    metric_passed = value >= threshold_value
                else:
                    # Default: pass if value <= threshold
                    metric_passed = value <= threshold_value
            
            threshold_checks[metric_name] = {
                "value": value,
                "threshold": threshold_display,  # Original value (bool, float, etc.)
                "passed": metric_passed
            }
            
            # Don't set passed = False here - we'll determine final passed based on GATE metrics only
            # This allows WARN metrics to not affect the exit code
    
    # Determine severity levels using policy profile if available
    profile = None
    if hasattr(config, 'domain_profile') and config.domain_profile:
        # Try to get policy profile from domain_profile
        threshold_level = getattr(config, 'threshold_profile', 'moderate')
        profile_id = f"{config.domain_profile}_{threshold_level}"
        profile = get_profile(profile_id)
    
    # If no profile found, try to infer from mode and task_type
    if not profile and hasattr(config, 'mode') and hasattr(config, 'task_type'):
        try:
            mode = AuditMode(config.mode) if config.mode else None
            task = TaskType(config.task_type) if config.task_type else None
            if mode and task:
                from audit.policy_profiles import list_profiles
                profiles = list_profiles(mode, task)
                if profiles:
                    # Use first matching profile (moderate by default)
                    profile = profiles[0]
        except (ValueError, AttributeError):
            pass  # If mode/task_type don't match enums, skip profile lookup
    
    # Special case: housing_valuation should match housing_price_moderate
    if not profile and hasattr(config, 'domain_profile') and config.domain_profile == 'housing_valuation':
        profile = get_profile('housing_price_moderate')
    
    # Classify metrics using enforcement analyzer
    gate_metrics = {}
    warn_metrics = {}
    report_metrics = {}
    
    # Add validity checks to gate_metrics (Layer 0)
    for metric_id, enforcement in validity_checks.items():
        gate_metrics[metric_id] = {
            "value": enforcement.value,
            "threshold": enforcement.threshold,
            "passed": enforcement.passed,
            "layer": enforcement.layer.value,
            "description": enforcement.description
        }
    
    # Classify thresholded metrics using enforcement analyzer
    for metric_name, check in threshold_checks.items():
        enforcement = enforcement_analyzer.classify_metric(
            metric_name,
            check["value"],
            check.get("threshold"),
            check.get("passed")
        )
        
        # Override with profile severity if available
        if profile:
            profile_severity = profile.get_severity(metric_name)
            if profile_severity == MetricSeverity.GATE:
                enforcement.layer = EnforcementLayer.PERFORMANCE if enforcement.layer == EnforcementLayer.TELEMETRY else enforcement.layer
            elif profile_severity == MetricSeverity.WARN:
                enforcement.layer = EnforcementLayer.RISK_SIGNALS
            elif profile_severity == MetricSeverity.REPORT:
                enforcement.layer = EnforcementLayer.TELEMETRY
        
        # Assign to appropriate category
        if enforcement.layer in [EnforcementLayer.VALIDITY, EnforcementLayer.DATA_INTEGRITY, 
                                  EnforcementLayer.PERFORMANCE, EnforcementLayer.FAIRNESS]:
            # Fairness gates are conditional - only gate if protected attributes exist
            if enforcement.layer == EnforcementLayer.FAIRNESS:
                if not has_protected_attrs:
                    # Move to WARN if no protected attributes
                    warn_metrics[metric_name] = {
                        **check,
                        "layer": EnforcementLayer.RISK_SIGNALS.value,
                        "description": enforcement.description or f"{metric_name} - fairness gate skipped (no protected attributes)"
                    }
                else:
                    gate_metrics[metric_name] = {
                        **check,
                        "layer": enforcement.layer.value,
                        "description": enforcement.description
                    }
            else:
                gate_metrics[metric_name] = {
                    **check,
                    "layer": enforcement.layer.value,
                    "description": enforcement.description
                }
        elif enforcement.layer == EnforcementLayer.RISK_SIGNALS:
            warn_metrics[metric_name] = {
                **check,
                "layer": enforcement.layer.value,
                "description": enforcement.description
            }
        else:
            report_metrics[metric_name] = {
                **check,
                "layer": enforcement.layer.value,
                "description": enforcement.description
            }
    
    # Add protected attributes check (Layer 4 - WARN) with actionable recommendations
    column_names = dataset_info.get('column_names', [])
    protected_attrs_check = enforcement_analyzer.check_protected_attributes(column_names=column_names)
    if protected_attrs_check:
        warn_metrics[protected_attrs_check.metric_id] = {
            "value": protected_attrs_check.value,
            "threshold": protected_attrs_check.threshold,
            "passed": protected_attrs_check.passed,
            "layer": protected_attrs_check.layer.value,
            "description": protected_attrs_check.description
        }
    
    # Check fairness prerequisites
    dataset_size = dataset_info.get('shape', {}).get('rows', 0)
    protected_columns = config.sensitive if hasattr(config, 'sensitive') else []
    # Get group sizes from fairness metrics if available
    group_sizes = None
    if category_metrics.get('fairness'):
        fairness_data = category_metrics['fairness']
        if 'group_sizes' in fairness_data:
            group_sizes = fairness_data['group_sizes']
    
    fairness_enabled, fairness_prereq_checks = enforcement_analyzer.check_fairness_prerequisites(
        dataset_size=dataset_size,
        protected_columns=protected_columns if protected_columns else None,
        group_sizes=group_sizes
    )
    
    # Add fairness prerequisite checks to appropriate categories
    for check in fairness_prereq_checks:
        if check.layer == EnforcementLayer.RISK_SIGNALS:
            warn_metrics[check.metric_id] = {
                "value": check.value,
                "threshold": check.threshold,
                "passed": check.passed,
                "layer": check.layer.value,
                "description": check.description
            }
        else:
            report_metrics[check.metric_id] = {
                "value": check.value,
                "threshold": check.threshold,
                "passed": check.passed,
                "description": check.description
            }
    
    # Skip fairness gates if prerequisites not met
    if not fairness_enabled:
        # Move fairness gates to WARN
        fairness_gates_to_warn = [k for k, v in gate_metrics.items() if v.get("layer") == EnforcementLayer.FAIRNESS.value]
        for metric_name in fairness_gates_to_warn:
            check = gate_metrics.pop(metric_name)
            warn_metrics[metric_name] = {
                **check,
                "description": check.get("description", "") + " (Skipped: fairness prerequisites not met)"
            }
    
    # Add dataset size warnings if applicable (Layer 4 - WARN)
    # Already handled in the loop above, but keeping for clarity
    
    # Check for drift without baseline (WARN)
    if category_metrics.get('data_quality', {}).get('drift', {}):
        drift = category_metrics['data_quality']['drift']
        if drift.get('error') and 'baseline' in str(drift.get('error', '')).lower():
            warn_metrics["drift_baseline_missing"] = {
                "value": True,
                "threshold": False,
                "passed": False,
                "layer": EnforcementLayer.RISK_SIGNALS.value,
                "description": "Distribution drift detection requires baseline dataset (not configured)"
            }
    
    # Add feature alignment details to report metrics
    if expected_count is not None:
        report_metrics['expected_features_count'] = {"value": expected_count, "threshold": None, "passed": None}
    if found_count is not None:
        report_metrics['found_features_count'] = {"value": found_count, "threshold": None, "passed": None}
    if extra_features:
        report_metrics['extra_columns_ignored'] = {"value": extra_features, "threshold": None, "passed": None}
    if missing_features:
        report_metrics['missing_features'] = {"value": missing_features, "threshold": None, "passed": None}
    
    # Add metrics without thresholds to REPORT
    for metric_name, value in metrics.items():
        if metric_name not in threshold_checks:
            report_metrics[metric_name] = {"value": value, "threshold": None, "passed": None}
    
    # Print summary with severity sections
    console.print("\n[bold]Audit Summary[/bold]\n")
    
    # GATE metrics (failures block audit) - grouped by layer
    if gate_metrics:
        # Group by layer
        layer_groups = {
            EnforcementLayer.VALIDITY: [],
            EnforcementLayer.DATA_INTEGRITY: [],
            EnforcementLayer.PERFORMANCE: [],
            EnforcementLayer.FAIRNESS: []
        }
        
        for metric_name, check in gate_metrics.items():
            layer_str = check.get("layer", "telemetry")
            try:
                layer = EnforcementLayer(layer_str)
                if layer in layer_groups:
                    layer_groups[layer].append((metric_name, check))
            except ValueError:
                # Unknown layer, skip grouping
                pass
        
        # Display each layer group
        layer_names = {
            EnforcementLayer.VALIDITY: "[bold red]Layer 0: Audit Validity (Hard GATE)[/bold red]",
            EnforcementLayer.DATA_INTEGRITY: "[bold red]Layer 1: Data Integrity (Default GATE)[/bold red]",
            EnforcementLayer.PERFORMANCE: "[bold red]Layer 2: Performance Fitness (Domain-aware GATE)[/bold red]",
            EnforcementLayer.FAIRNESS: "[bold red]Layer 3: Fairness & Governance (Conditional GATE)[/bold red]"
        }
        
        for layer, layer_name in layer_names.items():
            layer_metrics = layer_groups[layer]
            if layer_metrics:
                console.print(layer_name)
                table = Table(box=box.ROUNDED)
                table.add_column("Metric", style="cyan")
                table.add_column("Value", style="magenta")
                table.add_column("Threshold", style="yellow")
                table.add_column("Status", style="bold")
                
                for metric_name, check in layer_metrics:
                    if check["passed"] is not None:
                        status = "[green]PASS[/green]" if check["passed"] else "[red]FAIL[/red]"
                        # Format value based on type
                        if isinstance(check['value'], bool):
                            value_str = "True" if check['value'] else "False"
                        elif isinstance(check['value'], (int, float)):
                            value_str = f"{check['value']:.4f}" if isinstance(check['value'], float) else str(check['value'])
                        else:
                            value_str = str(check['value'])
                        
                        # Format threshold based on type
                        if isinstance(check['threshold'], bool):
                            threshold_str = "True" if check['threshold'] else "False"
                        elif isinstance(check['threshold'], (int, float)):
                            threshold_str = f"{check['threshold']:.4f}" if isinstance(check['threshold'], float) else str(check['threshold'])
                        else:
                            threshold_str = str(check['threshold']) if check['threshold'] is not None else "—"
                        
                        # Show description if available
                        description = check.get('description', '')
                        metric_display = f"{metric_name}\n[dim]{description}[/dim]" if description else metric_name
                        
                        table.add_row(
                            metric_display,
                            value_str,
                            threshold_str,
                            status
                        )
                
                console.print(table)
                console.print()
    
    # WARN metrics (warnings)
    if warn_metrics:
        console.print("[bold yellow]WARN (Risk Flags)[/bold yellow]")
        table = Table(box=box.ROUNDED)
        table.add_column("Metric", style="cyan")
        table.add_column("Value", style="magenta")
        table.add_column("Threshold", style="yellow")
        table.add_column("Status", style="bold")
        
        for metric_name, check in warn_metrics.items():
            if check["passed"] is not None:
                status = "[green]OK[/green]" if check["passed"] else "[yellow]WARN[/yellow]"
                # Format value based on type
                if isinstance(check['value'], bool):
                    value_str = "True" if check['value'] else "False"
                elif isinstance(check['value'], (int, float)):
                    value_str = f"{check['value']:.4f}" if isinstance(check['value'], float) else str(check['value'])
                else:
                    value_str = str(check['value'])
                
                # Format threshold based on type
                if isinstance(check['threshold'], bool):
                    threshold_str = "True" if check['threshold'] else "False"
                elif isinstance(check['threshold'], (int, float)):
                    threshold_str = f"{check['threshold']:.4f}" if isinstance(check['threshold'], float) else str(check['threshold'])
                else:
                    threshold_str = str(check['threshold']) if check['threshold'] is not None else "—"
                
                # Show description if available
                description = check.get('description', '')
                metric_display = f"{metric_name}\n[dim]{description}[/dim]" if description else metric_name
                
                table.add_row(
                    metric_display,
                    value_str,
                    threshold_str,
                    status
                )
        
        console.print(table)
        console.print()
    
    # REPORT metrics (telemetry only) - no thresholds shown
    if report_metrics:
        console.print("[dim]REPORT (Telemetry Only)[/dim]")
        table = Table(box=box.ROUNDED)
        table.add_column("Metric", style="cyan")
        table.add_column("Value", style="magenta")
        table.add_column("Status", style="dim")
        
        # Show first 10 report metrics to avoid clutter
        shown = 0
        for metric_name, check in report_metrics.items():
            if shown >= 10:
                break
            # Handle different value types safely
            value = check.get('value', 0)
            if isinstance(value, (int, float)):
                value_str = f"{value:.4f}" if isinstance(value, float) else str(value)
            else:
                value_str = str(value)
            table.add_row(
                metric_name,
                value_str,
                "[dim]REPORTED[/dim]"
            )
            shown += 1
        
        if len(report_metrics) > 10:
            table.add_row(
                f"... and {len(report_metrics) - 10} more",
                "",
                ""
            )
        
        console.print(table)
        console.print()
    
    # Convert gate_metrics and warn_metrics to MetricEnforcement objects for status determination
    gate_enforcements = {}
    for metric_id, check in gate_metrics.items():
        layer_str = check.get("layer", "telemetry")
        try:
            layer = EnforcementLayer(layer_str)
        except ValueError:
            # Default to TELEMETRY if layer is invalid
            layer = EnforcementLayer.TELEMETRY
        gate_enforcements[metric_id] = MetricEnforcement(
            metric_id=metric_id,
            layer=layer,
            value=check.get("value"),
            threshold=check.get("threshold"),
            passed=check.get("passed"),
            description=check.get("description")
        )
    
    warn_enforcements = {}
    for metric_id, check in warn_metrics.items():
        layer_str = check.get("layer", "risk_signals")
        try:
            layer = EnforcementLayer(layer_str)
        except ValueError:
            # Default to RISK_SIGNALS if layer is invalid
            layer = EnforcementLayer.RISK_SIGNALS
        warn_enforcements[metric_id] = MetricEnforcement(
            metric_id=metric_id,
            layer=layer,
            value=check.get("value"),
            threshold=check.get("threshold"),
            passed=check.get("passed"),
            description=check.get("description")
        )
    
    # Determine overall status using enforcement analyzer
    audit_status, confidence, reason = enforcement_analyzer.determine_overall_status(
        gate_enforcements,
        warn_enforcements
    )
    
    # Map to display strings
    if audit_status == AuditStatus.PASSED:
        overall_status = "[green]PASSED[/green]"
        passed = True
    elif audit_status == AuditStatus.PASSED_WITH_WARNINGS:
        overall_status = "[yellow]PASSED WITH WARNINGS[/yellow]"
        passed = True  # Still passes, but with warnings
    elif audit_status == AuditStatus.INCOMPLETE:
        overall_status = "[red]INCOMPLETE[/red]"
        passed = False
    else:  # FAILED
        overall_status = "[red]FAILED[/red]"
        passed = False
    
    console.print(f"\n[bold]Overall Status: {overall_status}[/bold]")
    if audit_status == AuditStatus.PASSED_WITH_WARNINGS:
        console.print(f"[dim]Confidence: {confidence:.0%}[/dim]")
    console.print(f"[dim]{reason}[/dim]")
    console.print("[dim]Enforcement layers: Validity → Data Integrity → Performance → Fairness[/dim]\n")
    
    # Override evidence_dir if output_dir is specified
    evidence_dir = Path(output_dir) if output_dir else config.evidence_dir
    
    # Get API key for upload
    api_key = os.getenv("VERITY_API_KEY")
    
    # Check if this is a local audit (no API key = local only)
    is_local_audit = not api_key and not upload
    
    # Check if evidence storage is enabled (from environment or config)
    # Pro/Enterprise plans have evidence storage, but only when uploaded to platform
    has_evidence_storage = (plan in ["pro", "enterprise"] and not is_local_audit) or (os.getenv("VERITY_EVIDENCE_STORAGE", "false").lower() == "true")
    has_api_access = api_key is not None  # If API key exists, API access is available
    
    # Governance checks and ACM ethics mapping only for Pro/Enterprise AND when uploaded
    # Local audits are intentionally incomplete - no governance checks or ethics mapping
    include_governance = (plan in ["pro", "enterprise"]) and not is_local_audit
    include_ethics = (plan in ["pro", "enterprise"]) and not is_local_audit
    
    # Write report with category-based structure
    # Only include governance checks and ethics mapping for Pro/Enterprise
    try:
        # Prepare enforcement metadata for report (convert to dict for serialization)
        enforcement_metadata = {
            "status": audit_status.value,
            "confidence": confidence,
            "reason": reason,
            "layers": {
                "validity": [m.to_dict() for m in gate_enforcements.values() if m.layer == EnforcementLayer.VALIDITY],
                "data_integrity": [m.to_dict() for m in gate_enforcements.values() if m.layer == EnforcementLayer.DATA_INTEGRITY],
                "performance": [m.to_dict() for m in gate_enforcements.values() if m.layer == EnforcementLayer.PERFORMANCE],
                "fairness": [m.to_dict() for m in gate_enforcements.values() if m.layer == EnforcementLayer.FAIRNESS],
                "risk_signals": [m.to_dict() for m in warn_enforcements.values() if m.layer == EnforcementLayer.RISK_SIGNALS]
            }
        }
        
        report_path = write_report(
            evidence_dir,
            category_metrics,  # Pass category_metrics (includes hybrid structure if applicable)
            config.thresholds,
            passed,
            config_path=config.config_path,
            dataset_path=config.dataset if config.mode != 'api_model' else None,
            model_path=config.model if config.mode != 'api_model' else None,
            label_col=config.label if config.mode != 'api_model' else None,
            sensitive_attrs=config.sensitive if config.mode != 'api_model' else None,
            dataset_info=dataset_info if config.mode != 'api_model' else None,
            dataset_metadata={
                "description": config.dataset_description,
                "population": config.dataset_population,
                "jurisdiction": config.dataset_jurisdiction,
                "sample_size": config.dataset_sample_size
            } if config.dataset_metadata else None,
            has_evidence_storage=has_evidence_storage,
            has_api_access=has_api_access,
            include_governance=include_governance,
            include_ethics=include_ethics,
            task_type=config.task_type,
            enforcement_metadata=enforcement_metadata,
            audit_intent=getattr(config, 'audit_intent', None),
            domain_profile=getattr(config, 'domain_profile', None)
        )
        console.print(f"[green]Report written to: {report_path}[/green]")
        
        # Reminder for local audits
        if is_local_audit:
            console.print("\n[yellow]⚠️  LOCAL AUDIT - VALIDATION ONLY[/yellow]")
            console.print("[dim]This local audit is for developer validation and debugging only.[/dim]")
            console.print("[dim]It does NOT provide:[/dim]")
            console.print("[dim]  • Audit history or trend analysis[/dim]")
            console.print("[dim]  • Comparison against past runs[/dim]")
            console.print("[dim]  • Regulator-ready summaries[/dim]")
            console.print("[dim]  • Signed evidence for compliance[/dim]")
            console.print("[dim]  • CI/CD enforcement capabilities[/dim]")
            console.print("[dim][/dim]")
            console.print("[dim]For governance enforcement, compliance evidence, and regulatory reporting,[/dim]")
            console.print("[dim]audits must be uploaded to Verity's platform.[/dim]")
            console.print("[dim]Local runs are advisory. Platform-recorded runs are authoritative.[/dim]\n")
    except Exception as e:
        console.print(f"[red]Error writing report: {e}[/red]")
        return False
    
    # Handle upload if requested or if API key is available
    should_upload = upload or (api_key is not None)
    
    if should_upload:
        # Use production API URL by default, not localhost
        api_url = os.getenv("VERITY_API_URL") or "https://verityai-production.up.railway.app"
        
        if not api_key:
            console.print("[yellow]VERITY_API_KEY not set - skipping upload (offline mode)[/yellow]")
        else:
            try:
                # Read the report we just wrote
                with open(report_path, 'r') as f:
                    report_data = json.load(f)
                
                # Extract commit context from GitHub Actions environment (if available)
                # These are set automatically by GitHub Actions
                commit_sha = os.getenv("GITHUB_SHA")
                branch = os.getenv("GITHUB_REF_NAME")
                repo = os.getenv("GITHUB_REPOSITORY")
                pr_number = os.getenv("GITHUB_PR_NUMBER") or os.getenv("GITHUB_EVENT_PULL_REQUEST_NUMBER")
                github_server = os.getenv("GITHUB_SERVER_URL", "https://github.com")
                run_id = os.getenv("GITHUB_RUN_ID")
                
                # Build workflow run URL if we have the necessary info
                workflow_run_url = None
                if github_server and repo and run_id:
                    workflow_run_url = f"{github_server}/{repo}/actions/runs/{run_id}"
                
                # Add commit context to report metadata if available
                if commit_sha or branch or repo or pr_number or workflow_run_url:
                    if "metadata" not in report_data:
                        report_data["metadata"] = {}
                    if commit_sha:
                        report_data["metadata"]["commit_sha"] = commit_sha
                        report_data["commit_sha"] = commit_sha  # Also at top level for convenience
                    if branch:
                        report_data["metadata"]["branch"] = branch
                        report_data["branch"] = branch
                    if repo:
                        report_data["metadata"]["repo"] = repo
                        report_data["repo"] = repo
                    if pr_number:
                        try:
                            report_data["metadata"]["pr_number"] = int(pr_number)
                            report_data["pr_number"] = int(pr_number)
                        except (ValueError, TypeError):
                            pass
                    if workflow_run_url:
                        report_data["metadata"]["workflow_run_url"] = workflow_run_url
                
                # Upload to API
                # API keys use X-API-Key header, not Bearer token
                headers = {
                    "X-API-Key": api_key,
                    "Content-Type": "application/json"
                }
                
                response = requests.post(
                    f"{api_url}/v1/audits",
                    json=report_data,
                    headers=headers,
                    timeout=10
                )
                
                if response.status_code == 200:
                    result = response.json()
                    console.print(f"[green]✓ Audit uploaded: {result.get('audit_id')}[/green]")
                    if commit_sha:
                        console.print(f"[dim]Commit: {commit_sha[:8]}[/dim]")
                    
                    # Plan was already detected before threshold filtering - don't print duplicate messages
                    # The plan detection and threshold filtering messages were already printed earlier
                elif response.status_code == 409:
                    console.print(f"[yellow]Audit already exists (append-only storage)[/yellow]")
                else:
                    console.print(f"[red]Upload failed: {response.status_code} - {response.text}[/red]")
            
            except requests.exceptions.RequestException as e:
                console.print(f"[red]Upload error: {e}[/red]")
                console.print("[yellow]Continuing in offline mode[/yellow]")
    
    # Return pass/fail based on fail_on_threshold flag
    if fail_on_threshold:
        return passed
    else:
        # If not failing on threshold, always return True (but still report the status)
        return True


def main():
    """Main CLI entry point."""
    parser = argparse.ArgumentParser(
        description="AI Model Audit Tool",
        epilog="""
LICENSE NOTICE:
Local audits are allowed for personal or internal use only.
Redistribution, resale, or hosted use is prohibited.

Local audits are for developer validation only. They do NOT provide:
- Audit history or trend analysis
- Comparison against past runs
- Regulator-ready summaries
- Signed evidence for compliance
- CI/CD enforcement capabilities

For governance enforcement, compliance evidence, and regulatory reporting,
audits must be uploaded to Verity's platform. Local runs are advisory.
Platform-recorded runs are authoritative.

For licensing questions: licensing@verity.com
        """,
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    subparsers = parser.add_subparsers(dest="command", help="Command to run")
    
    # Run command
    run_parser = subparsers.add_parser("run", help="Run audit")
    run_parser.add_argument(
        "--config",
        required=True,
        help="Path to configuration YAML file"
    )
    run_parser.add_argument(
        "--fail-on-threshold",
        action="store_true",
        default=True,
        help="Exit with code 1 if any metric fails threshold (default: True)"
    )
    run_parser.add_argument(
        "--no-fail-on-threshold",
        dest="fail_on_threshold",
        action="store_false",
        help="Do not exit with code 1 on threshold failure"
    )
    run_parser.add_argument(
        "--output-dir",
        type=str,
        help="Directory to write audit evidence (overrides config)"
    )
    run_parser.add_argument(
        "--upload",
        action="store_true",
        default=False,
        help="Upload audit results (currently offline mode - no-op)"
    )
    run_parser.add_argument(
        "--no-upload",
        dest="upload",
        action="store_false",
        help="Do not upload audit results (default)"
    )
    run_parser.add_argument(
        "--export",
        choices=["pdf", "json"],
        help="Export report in specified format (validation only for local audits)"
    )
    
    args = parser.parse_args()
    
    if args.command == "run":
        passed = run_audit(
            args.config,
            fail_on_threshold=args.fail_on_threshold,
            output_dir=args.output_dir,
            upload=args.upload
        )
        
        # Handle export if requested
        if args.export:
            # Reload config to get evidence_dir
            config = Config(args.config)
            evidence_dir = Path(args.output_dir) if args.output_dir else config.evidence_dir
            report_path = evidence_dir / "report.json"
            
            if not report_path.exists():
                console.print(f"[red]Report not found: {report_path}[/red]")
                sys.exit(1)
            
            # Load report to get audit_id
            with open(report_path, 'r') as f:
                report = json.load(f)
            
            try:
                # Check if this is a local audit
                is_local = not report.get("evidence_metadata", {}).get("retention_status") == "active"
                
                if args.export == "pdf":
                    export_path = evidence_dir / f"audit_report_{report.get('audit_id', 'unknown')[:8]}.pdf"
                    export_pdf(report_path, export_path, config.config_path)
                    console.print(f"[green]PDF export written to: {export_path}[/green]")
                    if is_local:
                        console.print("[yellow]⚠️  This is a local validation export. It does not provide signed evidence or regulator-ready summaries.[/yellow]")
                elif args.export == "json":
                    export_path = evidence_dir / f"audit_report_{report.get('audit_id', 'unknown')[:8]}.json"
                    export_json(report_path, export_path, config.config_path)
                    console.print(f"[green]JSON export written to: {export_path}[/green]")
                    if is_local:
                        console.print("[yellow]⚠️  This is a local validation export. It does not provide signed evidence or regulator-ready summaries.[/yellow]")
            except Exception as e:
                console.print(f"[red]Export error: {e}[/red]")
                import traceback
                console.print(f"[red]{traceback.format_exc()}[/red]")
                sys.exit(1)
        
        # Ensure exit codes: 0 = pass, 1 = fail
        sys.exit(0 if passed else 1)
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()

