"""
Robust OP2 Extraction - Handles pyNastran FATAL flag issues gracefully.

This module provides a more robust OP2 extraction that:
1. Catches pyNastran FATAL flag exceptions
2. Checks if eigenvalues were actually extracted despite the flag
3. Falls back to F06 extraction if OP2 fails
4. Logs detailed failure information

Usage:
    from optimization_engine.extractors.op2_extractor import robust_extract_first_frequency

    frequency = robust_extract_first_frequency(
        op2_file=Path("results.op2"),
        mode_number=1,
        f06_file=Path("results.f06"),  # Optional fallback
        verbose=True
    )
"""

from pathlib import Path
from typing import Optional, Tuple
import numpy as np


def robust_extract_first_frequency(
    op2_file: Path,
    mode_number: int = 1,
    f06_file: Optional[Path] = None,
    verbose: bool = False
) -> float:
    """
    Robustly extract natural frequency from OP2 file, handling pyNastran issues.

    This function attempts multiple strategies:
    1. Standard pyNastran OP2 reading
    2. Force reading with debug=False to ignore FATAL flags
    3. Partial OP2 reading (extract eigenvalues even if FATAL flag exists)
    4. Fallback to F06 file parsing (if provided)

    Args:
        op2_file: Path to OP2 output file
        mode_number: Mode number to extract (1-based index)
        f06_file: Optional F06 file for fallback extraction
        verbose: Print detailed extraction information

    Returns:
        Natural frequency in Hz

    Raises:
        ValueError: If frequency cannot be extracted by any method
    """
    from pyNastran.op2.op2 import OP2

    if not op2_file.exists():
        raise FileNotFoundError(f"OP2 file not found: {op2_file}")

    # Strategy 1: Try standard OP2 reading
    try:
        if verbose:
            print(f"[OP2 EXTRACT] Attempting standard read: {op2_file.name}")

        model = OP2()
        model.read_op2(str(op2_file))

        if hasattr(model, 'eigenvalues') and len(model.eigenvalues) > 0:
            frequency = _extract_frequency_from_model(model, mode_number)
            if verbose:
                print(f"[OP2 EXTRACT] ✓ Success (standard read): {frequency:.6f} Hz")
            return frequency
        else:
            raise ValueError("No eigenvalues found in OP2 file")

    except Exception as e:
        if verbose:
            print(f"[OP2 EXTRACT] ✗ Standard read failed: {str(e)[:100]}")

        # Check if this is a FATAL flag issue
        is_fatal_flag = 'FATAL' in str(e) and 'op2_reader' in str(e.__class__.__module__)

        if is_fatal_flag:
            # Strategy 2: Try reading with more lenient settings
            if verbose:
                print(f"[OP2 EXTRACT] Detected pyNastran FATAL flag issue")
                print(f"[OP2 EXTRACT] Attempting partial extraction...")

            try:
                model = OP2()
                # Try to read with debug=False and skip_undefined_matrices=True
                model.read_op2(
                    str(op2_file),
                    debug=False,
                    skip_undefined_matrices=True
                )

                # Check if eigenvalues were extracted despite FATAL
                if hasattr(model, 'eigenvalues') and len(model.eigenvalues) > 0:
                    frequency = _extract_frequency_from_model(model, mode_number)
                    if verbose:
                        print(f"[OP2 EXTRACT] ✓ Success (lenient mode): {frequency:.6f} Hz")
                        print(f"[OP2 EXTRACT] Note: pyNastran reported FATAL but data is valid!")
                    return frequency

            except Exception as e2:
                if verbose:
                    print(f"[OP2 EXTRACT] ✗ Lenient read also failed: {str(e2)[:100]}")

        # Strategy 3: Fallback to F06 parsing
        if f06_file and f06_file.exists():
            if verbose:
                print(f"[OP2 EXTRACT] Falling back to F06 extraction: {f06_file.name}")

            try:
                frequency = extract_frequency_from_f06(f06_file, mode_number, verbose=verbose)
                if verbose:
                    print(f"[OP2 EXTRACT] ✓ Success (F06 fallback): {frequency:.6f} Hz")
                return frequency

            except Exception as e3:
                if verbose:
                    print(f"[OP2 EXTRACT] ✗ F06 extraction failed: {str(e3)}")

        # All strategies failed
        raise ValueError(
            f"Could not extract frequency from OP2 file: {op2_file.name}. "
            f"Original error: {str(e)}"
        )


def _extract_frequency_from_model(model, mode_number: int) -> float:
    """Extract frequency from loaded OP2 model."""
    if not hasattr(model, 'eigenvalues') or len(model.eigenvalues) == 0:
        raise ValueError("No eigenvalues found in model")

    # Get first subcase
    subcase = list(model.eigenvalues.keys())[0]
    eig_obj = model.eigenvalues[subcase]

    # Check if mode exists
    if mode_number > len(eig_obj.eigenvalues):
        raise ValueError(
            f"Mode {mode_number} not found. "
            f"Only {len(eig_obj.eigenvalues)} modes available"
        )

    # Extract eigenvalue and convert to frequency
    eigenvalue = eig_obj.eigenvalues[mode_number - 1]
    angular_freq = np.sqrt(abs(eigenvalue))  # Use abs to handle numerical precision issues
    frequency_hz = angular_freq / (2 * np.pi)

    return float(frequency_hz)


def extract_frequency_from_f06(
    f06_file: Path,
    mode_number: int = 1,
    verbose: bool = False
) -> float:
    """
    Extract natural frequency from F06 text file (fallback method).

    Parses the F06 file to find eigenvalue results table and extracts frequency.

    Args:
        f06_file: Path to F06 output file
        mode_number: Mode number to extract (1-based index)
        verbose: Print extraction details

    Returns:
        Natural frequency in Hz

    Raises:
        ValueError: If frequency cannot be found in F06
    """
    if not f06_file.exists():
        raise FileNotFoundError(f"F06 file not found: {f06_file}")

    with open(f06_file, 'r', encoding='latin-1', errors='ignore') as f:
        content = f.read()

    # Look for eigenvalue table
    # Nastran F06 format has eigenvalue results like:
    #                                             R E A L   E I G E N V A L U E S
    #    MODE    EXTRACTION      EIGENVALUE            RADIANS             CYCLES            GENERALIZED         GENERALIZED
    #     NO.       ORDER                                                                       MASS              STIFFNESS
    #         1         1       -6.602743E+04        2.569656E+02        4.089338E+01        1.000000E+00        6.602743E+04

    lines = content.split('\n')

    # Find eigenvalue table
    eigenvalue_section_start = None
    for i, line in enumerate(lines):
        if 'R E A L   E I G E N V A L U E S' in line:
            eigenvalue_section_start = i
            break

    if eigenvalue_section_start is None:
        raise ValueError("Eigenvalue table not found in F06 file")

    # Parse eigenvalue table (starts a few lines after header)
    for i in range(eigenvalue_section_start + 3, min(eigenvalue_section_start + 100, len(lines))):
        line = lines[i].strip()

        if not line or line.startswith('1'):  # Page break
            continue

        # Parse line with mode data
        parts = line.split()
        if len(parts) >= 5:
            try:
                mode_num = int(parts[0])
                if mode_num == mode_number:
                    # Frequency is in column 5 (CYCLES)
                    frequency = float(parts[4])
                    if verbose:
                        print(f"[F06 EXTRACT] Found mode {mode_num}: {frequency:.6f} Hz")
                    return frequency
            except (ValueError, IndexError):
                continue

    raise ValueError(f"Mode {mode_number} not found in F06 eigenvalue table")


def validate_op2_file(op2_file: Path, f06_file: Optional[Path] = None) -> Tuple[bool, str]:
    """
    Validate if an OP2 file contains usable eigenvalue data.

    Args:
        op2_file: Path to OP2 file
        f06_file: Optional F06 file for cross-reference

    Returns:
        (is_valid, message): Tuple of validation status and explanation
    """
    if not op2_file.exists():
        return False, f"OP2 file does not exist: {op2_file}"

    if op2_file.stat().st_size == 0:
        return False, "OP2 file is empty"

    # Try to extract first frequency
    try:
        frequency = robust_extract_first_frequency(
            op2_file,
            mode_number=1,
            f06_file=f06_file,
            verbose=False
        )
        return True, f"Valid OP2 file (first frequency: {frequency:.6f} Hz)"

    except Exception as e:
        return False, f"Cannot extract data from OP2: {str(e)}"


# Convenience function (same signature as old function for backward compatibility)
def extract_first_frequency(op2_file: Path, mode_number: int = 1) -> float:
    """
    Extract first natural frequency (backward compatible with old function).

    This is the simple version - just use robust_extract_first_frequency directly
    for more control.

    Args:
        op2_file: Path to OP2 file
        mode_number: Mode number (1-based)

    Returns:
        Frequency in Hz
    """
    # Try to find F06 file in same directory
    f06_file = op2_file.with_suffix('.f06')

    return robust_extract_first_frequency(
        op2_file,
        mode_number=mode_number,
        f06_file=f06_file if f06_file.exists() else None,
        verbose=False
    )
