"""
NX Open Hooks - Usage Examples
==============================

This file contains practical examples of using the NX Open hooks
for common optimization tasks.

Run examples:
    python -m optimization_engine.hooks.examples

Or import specific examples:
    from optimization_engine.hooks.examples import design_exploration_example
"""

import os
import sys
from pathlib import Path

# Add project root to path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))

from optimization_engine.hooks.nx_cad import (
    part_manager,
    expression_manager,
    geometry_query,
    feature_manager,
)


# =============================================================================
# Example 1: Basic Expression Query
# =============================================================================

def basic_expression_query(part_path: str):
    """
    Example: Query all expressions from an NX part.

    This is useful for discovering available design parameters
    before setting up an optimization study.
    """
    print("\n" + "=" * 60)
    print("Example 1: Basic Expression Query")
    print("=" * 60)

    result = expression_manager.get_expressions(part_path)

    if not result["success"]:
        print(f"ERROR: {result['error']}")
        return None

    data = result["data"]
    print(f"\nFound {data['count']} expressions:\n")

    # Print in a nice table format
    print(f"{'Name':<25} {'Value':>12} {'Units':<15} {'RHS'}")
    print("-" * 70)

    for name, expr in data["expressions"].items():
        units = expr.get("units") or ""
        rhs = expr.get("rhs", "")
        # Truncate RHS if it's a formula reference
        if len(rhs) > 20:
            rhs = rhs[:17] + "..."
        print(f"{name:<25} {expr['value']:>12.4f} {units:<15} {rhs}")

    return data["expressions"]


# =============================================================================
# Example 2: Mass Properties Extraction
# =============================================================================

def mass_properties_example(part_path: str):
    """
    Example: Extract mass properties from an NX part.

    This is useful for mass optimization objectives.
    """
    print("\n" + "=" * 60)
    print("Example 2: Mass Properties Extraction")
    print("=" * 60)

    result = geometry_query.get_mass_properties(part_path)

    if not result["success"]:
        print(f"ERROR: {result['error']}")
        return None

    data = result["data"]

    print(f"\nMass Properties:")
    print("-" * 40)
    print(f"  Mass:         {data['mass']:.6f} {data['mass_unit']}")
    print(f"  Volume:       {data['volume']:.2f} {data['volume_unit']}")
    print(f"  Surface Area: {data['surface_area']:.2f} {data['area_unit']}")
    print(f"  Material:     {data['material'] or 'Not assigned'}")

    centroid = data["centroid"]
    print(f"\nCentroid (mm):")
    print(f"  X: {centroid['x']:.4f}")
    print(f"  Y: {centroid['y']:.4f}")
    print(f"  Z: {centroid['z']:.4f}")

    if data.get("principal_moments"):
        pm = data["principal_moments"]
        print(f"\nPrincipal Moments of Inertia ({pm['unit']}):")
        print(f"  Ixx: {pm['Ixx']:.4f}")
        print(f"  Iyy: {pm['Iyy']:.4f}")
        print(f"  Izz: {pm['Izz']:.4f}")

    return data


# =============================================================================
# Example 3: Design Parameter Update
# =============================================================================

def design_update_example(part_path: str, dry_run: bool = True):
    """
    Example: Update design parameters in an NX part.

    This demonstrates the workflow for parametric optimization:
    1. Read current values
    2. Compute new values
    3. Update the model

    Args:
        part_path: Path to the NX part
        dry_run: If True, only shows what would be changed (default)
    """
    print("\n" + "=" * 60)
    print("Example 3: Design Parameter Update")
    print("=" * 60)

    # Step 1: Get current expressions
    result = expression_manager.get_expressions(part_path)
    if not result["success"]:
        print(f"ERROR: {result['error']}")
        return None

    expressions = result["data"]["expressions"]

    # Step 2: Find numeric expressions (potential design variables)
    design_vars = {}
    for name, expr in expressions.items():
        # Skip linked expressions (RHS contains another expression name)
        if expr.get("rhs") and not expr["rhs"].replace(".", "").replace("-", "").isdigit():
            continue
        # Only include length/angle expressions
        if expr.get("units") in ["MilliMeter", "Degrees", None]:
            design_vars[name] = expr["value"]

    print(f"\nIdentified {len(design_vars)} potential design variables:")
    for name, value in design_vars.items():
        print(f"  {name}: {value}")

    if dry_run:
        print("\n[DRY RUN] Would update expressions (no changes made)")

        # Example: increase all dimensions by 10%
        new_values = {name: value * 1.1 for name, value in design_vars.items()}

        print("\nProposed changes:")
        for name, new_val in new_values.items():
            old_val = design_vars[name]
            print(f"  {name}: {old_val:.4f} -> {new_val:.4f} (+10%)")

        return new_values

    else:
        # Actually update the model
        new_values = {name: value * 1.1 for name, value in design_vars.items()}

        print("\nUpdating expressions...")
        result = expression_manager.set_expressions(part_path, new_values)

        if result["success"]:
            print(f"SUCCESS: Updated {result['data']['update_count']} expressions")
            if result["data"].get("errors"):
                print(f"Warnings: {result['data']['errors']}")
        else:
            print(f"ERROR: {result['error']}")

        return result


# =============================================================================
# Example 4: Feature Exploration
# =============================================================================

def feature_exploration_example(part_path: str):
    """
    Example: Explore and manipulate features.

    This is useful for topological optimization where features
    can be suppressed/unsuppressed to explore design space.
    """
    print("\n" + "=" * 60)
    print("Example 4: Feature Exploration")
    print("=" * 60)

    result = feature_manager.get_features(part_path)

    if not result["success"]:
        print(f"ERROR: {result['error']}")
        return None

    data = result["data"]

    print(f"\nFound {data['count']} features ({data['suppressed_count']} suppressed):\n")

    print(f"{'Name':<30} {'Type':<20} {'Status'}")
    print("-" * 60)

    for feat in data["features"]:
        status = "SUPPRESSED" if feat["is_suppressed"] else "Active"
        print(f"{feat['name']:<30} {feat['type']:<20} {status}")

    # Group by type
    print("\n\nFeatures by type:")
    print("-" * 40)
    type_counts = {}
    for feat in data["features"]:
        feat_type = feat["type"]
        type_counts[feat_type] = type_counts.get(feat_type, 0) + 1

    for feat_type, count in sorted(type_counts.items(), key=lambda x: -x[1]):
        print(f"  {feat_type}: {count}")

    return data


# =============================================================================
# Example 5: Optimization Objective Evaluation
# =============================================================================

def evaluate_design_point(part_path: str, parameters: dict) -> dict:
    """
    Example: Complete design evaluation workflow.

    This demonstrates how hooks integrate into an optimization loop:
    1. Update parameters
    2. Extract objectives (mass, volume)
    3. Return metrics

    Args:
        part_path: Path to the NX part
        parameters: Dict of parameter_name -> new_value

    Returns:
        Dict with mass_kg, volume_mm3, surface_area_mm2
    """
    print("\n" + "=" * 60)
    print("Example 5: Optimization Objective Evaluation")
    print("=" * 60)

    print(f"\nParameters to set:")
    for name, value in parameters.items():
        print(f"  {name} = {value}")

    # Step 1: Update parameters
    print("\n[1/2] Updating design parameters...")
    result = expression_manager.set_expressions(part_path, parameters)

    if not result["success"]:
        raise RuntimeError(f"Failed to set expressions: {result['error']}")

    print(f"  Updated {result['data']['update_count']} expressions")

    # Step 2: Extract objectives
    print("\n[2/2] Extracting mass properties...")
    result = geometry_query.get_mass_properties(part_path)

    if not result["success"]:
        raise RuntimeError(f"Failed to get mass properties: {result['error']}")

    data = result["data"]

    # Return metrics
    metrics = {
        "mass_kg": data["mass"],
        "volume_mm3": data["volume"],
        "surface_area_mm2": data["surface_area"],
        "material": data.get("material"),
    }

    print(f"\nObjective metrics:")
    print(f"  Mass:         {metrics['mass_kg']:.6f} kg")
    print(f"  Volume:       {metrics['volume_mm3']:.2f} mm^3")
    print(f"  Surface Area: {metrics['surface_area_mm2']:.2f} mm^2")

    return metrics


# =============================================================================
# Example 6: Batch Processing Multiple Parts
# =============================================================================

def batch_mass_extraction(part_paths: list) -> list:
    """
    Example: Extract mass from multiple parts.

    Useful for comparing variants or processing a design library.
    """
    print("\n" + "=" * 60)
    print("Example 6: Batch Processing Multiple Parts")
    print("=" * 60)

    results = []

    for i, part_path in enumerate(part_paths, 1):
        print(f"\n[{i}/{len(part_paths)}] Processing: {Path(part_path).name}")

        result = geometry_query.get_mass_properties(part_path)

        if result["success"]:
            data = result["data"]
            results.append({
                "part": Path(part_path).name,
                "mass_kg": data["mass"],
                "volume_mm3": data["volume"],
                "material": data.get("material"),
                "success": True,
            })
            print(f"  Mass: {data['mass']:.4f} kg, Material: {data.get('material')}")
        else:
            results.append({
                "part": Path(part_path).name,
                "error": result["error"],
                "success": False,
            })
            print(f"  ERROR: {result['error']}")

    # Summary
    print("\n" + "-" * 60)
    print("Summary:")
    successful = [r for r in results if r["success"]]
    print(f"  Processed: {len(successful)}/{len(part_paths)} parts")

    if successful:
        total_mass = sum(r["mass_kg"] for r in successful)
        print(f"  Total mass: {total_mass:.4f} kg")

    return results


# =============================================================================
# Main - Run All Examples
# =============================================================================

def main():
    """Run all examples with a test part."""

    # Default test part
    default_part = project_root / "studies/bracket_stiffness_optimization_V3/1_setup/model/Bracket.prt"

    if len(sys.argv) > 1:
        part_path = sys.argv[1]
    else:
        part_path = str(default_part)

    print("\n" + "=" * 60)
    print("NX OPEN HOOKS - EXAMPLES")
    print("=" * 60)
    print(f"\nUsing part: {Path(part_path).name}")

    if not os.path.exists(part_path):
        print(f"\nERROR: Part file not found: {part_path}")
        print("\nUsage: python -m optimization_engine.hooks.examples [part_path]")
        sys.exit(1)

    # Run examples
    try:
        # Example 1: Query expressions
        basic_expression_query(part_path)

        # Example 2: Get mass properties
        mass_properties_example(part_path)

        # Example 3: Design update (dry run)
        design_update_example(part_path, dry_run=True)

        # Example 4: Feature exploration
        feature_exploration_example(part_path)

        print("\n" + "=" * 60)
        print("ALL EXAMPLES COMPLETED SUCCESSFULLY!")
        print("=" * 60)

    except Exception as e:
        print(f"\nEXAMPLE FAILED: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()
