"""
Part Introspection Extractor (E12)
===================================

Comprehensive introspection of NX .prt files. Extracts:
- All expressions (user and internal)
- Mass properties (mass, volume, surface area, CoG)
- Material properties
- Body information
- Part attributes
- Groups
- Features summary
- Datum planes and coordinate systems
- Unit system
- Linked/associated parts

Usage:
    from optimization_engine.extractors.introspect_part import introspect_part

    result = introspect_part("path/to/model.prt")
    print(f"Mass: {result['mass_properties']['mass_kg']} kg")
    print(f"Expressions: {result['expressions']['user_count']}")

Dependencies:
    - NX installed with run_journal.exe
    - SPLM_LICENSE_SERVER environment variable

Author: Atomizer
Created: 2025-12-19
Version: 1.0
"""

import os
import json
import subprocess
import time
from pathlib import Path
from typing import Dict, Any, Optional


# NX installation path
NX_INSTALL_PATH = r"C:\Program Files\Siemens\DesigncenterNX2512"
RUN_JOURNAL_EXE = os.path.join(NX_INSTALL_PATH, "NXBIN", "run_journal.exe")
JOURNAL_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
                             "nx_journals", "introspect_part.py")

# License server
LICENSE_SERVER = "28000@dalidou;28000@100.80.199.40"


def introspect_part(
    prt_file_path: str,
    output_dir: Optional[str] = None,
    timeout_seconds: int = 120,
    verbose: bool = True
) -> Dict[str, Any]:
    """
    Run comprehensive introspection on an NX .prt file.

    Args:
        prt_file_path: Path to the .prt file
        output_dir: Directory for output JSON (defaults to prt directory)
        timeout_seconds: Maximum time to wait for journal execution
        verbose: Print progress messages

    Returns:
        Dictionary with introspection results:
        {
            'success': bool,
            'part_file': str,
            'expressions': {
                'user': [...],
                'internal': [...],
                'user_count': int,
                'total_count': int
            },
            'mass_properties': {
                'mass_kg': float,
                'mass_g': float,
                'volume_mm3': float,
                'surface_area_mm2': float,
                'center_of_gravity_mm': [x, y, z]
            },
            'materials': {
                'assigned': [...],
                'available': [...]
            },
            'bodies': {
                'solid_bodies': [...],
                'sheet_bodies': [...],
                'counts': {...}
            },
            'attributes': [...],
            'groups': [...],
            'features': {
                'total_count': int,
                'by_type': {...}
            },
            'datums': {...},
            'units': {...},
            'linked_parts': {...}
        }

    Raises:
        FileNotFoundError: If prt file doesn't exist
        RuntimeError: If journal execution fails
    """
    prt_path = Path(prt_file_path)
    if not prt_path.exists():
        raise FileNotFoundError(f"Part file not found: {prt_file_path}")

    if output_dir is None:
        output_dir = str(prt_path.parent)

    output_file = os.path.join(output_dir, "_temp_introspection.json")

    # Remove old output file if exists
    if os.path.exists(output_file):
        os.remove(output_file)

    if verbose:
        print(f"[INTROSPECT] Part: {prt_path.name}")
        print(f"[INTROSPECT] Output: {output_dir}")

    # Build PowerShell command (learned workaround - see LAC)
    # Using [Environment]::SetEnvironmentVariable() for reliable license server setting
    ps_command = (
        f"[Environment]::SetEnvironmentVariable('SPLM_LICENSE_SERVER', '{LICENSE_SERVER}', 'Process'); "
        f"& '{RUN_JOURNAL_EXE}' '{JOURNAL_PATH}' -args '{prt_file_path}' '{output_dir}' 2>&1"
    )

    cmd = ["powershell", "-Command", ps_command]

    if verbose:
        print(f"[INTROSPECT] Executing NX journal...")

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_seconds
        )

        if verbose and result.stdout:
            # Print key lines from output
            for line in result.stdout.split('\n'):
                if '[INTROSPECT]' in line:
                    print(line)

        if result.returncode != 0 and verbose:
            print(f"[INTROSPECT] Warning: Non-zero return code: {result.returncode}")
            if result.stderr:
                print(f"[INTROSPECT] Stderr: {result.stderr[:500]}")

    except subprocess.TimeoutExpired:
        raise RuntimeError(f"Journal execution timed out after {timeout_seconds}s")
    except Exception as e:
        raise RuntimeError(f"Journal execution failed: {e}")

    # Wait for output file (NX may still be writing)
    wait_start = time.time()
    while not os.path.exists(output_file) and (time.time() - wait_start) < 10:
        time.sleep(0.5)

    if not os.path.exists(output_file):
        raise RuntimeError(f"Introspection output file not created: {output_file}")

    # Read and return results
    with open(output_file, 'r') as f:
        results = json.load(f)

    if verbose:
        if results.get('success'):
            print(f"[INTROSPECT] Success!")
            print(f"[INTROSPECT]   Expressions: {results['expressions']['user_count']} user")
            print(f"[INTROSPECT]   Mass: {results['mass_properties']['mass_kg']:.4f} kg")
        else:
            print(f"[INTROSPECT] Failed: {results.get('error', 'Unknown error')}")

    return results


def get_expressions_dict(introspection_result: Dict[str, Any]) -> Dict[str, float]:
    """
    Convert introspection result to simple {name: value} dictionary of user expressions.

    Args:
        introspection_result: Result from introspect_part()

    Returns:
        Dictionary mapping expression names to values
    """
    expressions = {}
    for expr in introspection_result.get('expressions', {}).get('user', []):
        name = expr.get('name')
        value = expr.get('value')
        if name and value is not None:
            expressions[name] = value
    return expressions


def get_expression_value(introspection_result: Dict[str, Any], name: str) -> Optional[float]:
    """
    Get a specific expression value from introspection result.

    Args:
        introspection_result: Result from introspect_part()
        name: Expression name

    Returns:
        Expression value or None if not found
    """
    for expr in introspection_result.get('expressions', {}).get('user', []):
        if expr.get('name') == name:
            return expr.get('value')
    return None


def print_introspection_summary(result: Dict[str, Any]) -> None:
    """Print a formatted summary of introspection results."""
    print("\n" + "="*60)
    print(f"INTROSPECTION SUMMARY: {result.get('part_file', 'Unknown')}")
    print("="*60)

    # Mass
    mp = result.get('mass_properties', {})
    print(f"\nMASS PROPERTIES:")
    print(f"  Mass: {mp.get('mass_kg', 0):.4f} kg ({mp.get('mass_g', 0):.2f} g)")
    print(f"  Volume: {mp.get('volume_mm3', 0):.2f} mm³")
    print(f"  Surface Area: {mp.get('surface_area_mm2', 0):.2f} mm²")
    cog = mp.get('center_of_gravity_mm', [0, 0, 0])
    print(f"  Center of Gravity: [{cog[0]:.2f}, {cog[1]:.2f}, {cog[2]:.2f}] mm")

    # Bodies
    bodies = result.get('bodies', {})
    counts = bodies.get('counts', {})
    print(f"\nBODIES:")
    print(f"  Solid: {counts.get('solid', 0)}")
    print(f"  Sheet: {counts.get('sheet', 0)}")

    # Materials
    mats = result.get('materials', {})
    print(f"\nMATERIALS:")
    for mat in mats.get('assigned', []):
        print(f"  {mat.get('name', 'Unknown')}")
        props = mat.get('properties', {})
        if 'Density' in props:
            print(f"    Density: {props['Density']}")
        if 'YoungModulus' in props:
            print(f"    Young's Modulus: {props['YoungModulus']}")

    # Expressions
    exprs = result.get('expressions', {})
    print(f"\nEXPRESSIONS: {exprs.get('user_count', 0)} user, {len(exprs.get('internal', []))} internal")
    user_exprs = exprs.get('user', [])
    if user_exprs:
        print("  User expressions (first 10):")
        for expr in user_exprs[:10]:
            units = f" [{expr.get('units', '')}]" if expr.get('units') else ""
            print(f"    {expr.get('name')}: {expr.get('value')}{units}")
        if len(user_exprs) > 10:
            print(f"    ... and {len(user_exprs) - 10} more")

    # Features
    feats = result.get('features', {})
    print(f"\nFEATURES: {feats.get('total_count', 0)} total")
    by_type = feats.get('by_type', {})
    if by_type:
        top_types = sorted(by_type.items(), key=lambda x: x[1], reverse=True)[:5]
        for feat_type, count in top_types:
            print(f"  {feat_type}: {count}")

    # Units
    units = result.get('units', {})
    print(f"\nUNITS: {units.get('system', 'Unknown')}")

    print("="*60 + "\n")


# CLI interface
if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python introspect_part.py <prt_file_path> [output_dir]")
        sys.exit(1)

    prt_path = sys.argv[1]
    out_dir = sys.argv[2] if len(sys.argv) > 2 else None

    try:
        result = introspect_part(prt_path, out_dir, verbose=True)
        print_introspection_summary(result)
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)
