"""
Error Tracker Hook - Context Engineering Integration

Preserves solver errors and failures in context for learning.
Based on Manus insight: "leave the wrong turns in the context"

This hook:
1. Captures solver errors and failures
2. Classifies error types for playbook categorization
3. Extracts relevant F06 content for analysis
4. Records errors to session state and LAC

Hook Point: post_solve
Priority: 100 (run early to capture before cleanup)
"""

from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional
import json
import re


def classify_error(error_msg: str) -> str:
    """
    Classify error type for playbook categorization.

    Args:
        error_msg: Error message text

    Returns:
        Error classification string
    """
    error_lower = error_msg.lower()

    # Check patterns in priority order
    if any(x in error_lower for x in ['convergence', 'did not converge', 'diverge']):
        return "convergence_failure"
    elif any(x in error_lower for x in ['mesh', 'element', 'distorted', 'jacobian']):
        return "mesh_error"
    elif any(x in error_lower for x in ['singular', 'matrix', 'pivot', 'ill-conditioned']):
        return "singularity"
    elif any(x in error_lower for x in ['memory', 'allocation', 'out of memory']):
        return "memory_error"
    elif any(x in error_lower for x in ['license', 'checkout']):
        return "license_error"
    elif any(x in error_lower for x in ['boundary', 'constraint', 'spc', 'rigid body']):
        return "boundary_condition_error"
    elif any(x in error_lower for x in ['timeout', 'time limit']):
        return "timeout_error"
    elif any(x in error_lower for x in ['file', 'not found', 'missing']):
        return "file_error"
    else:
        return "unknown_error"


def extract_f06_error(f06_path: Optional[str], max_chars: int = 500) -> str:
    """
    Extract error section from F06 file.

    Args:
        f06_path: Path to F06 file
        max_chars: Maximum characters to extract

    Returns:
        Error section content or empty string
    """
    if not f06_path:
        return ""

    path = Path(f06_path)
    if not path.exists():
        return ""

    try:
        with open(path, 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read()

        # Look for error indicators
        error_markers = [
            "*** USER FATAL",
            "*** SYSTEM FATAL",
            "*** USER WARNING",
            "*** SYSTEM WARNING",
            "FATAL ERROR",
            "ERROR MESSAGE"
        ]

        for marker in error_markers:
            if marker in content:
                idx = content.index(marker)
                # Extract surrounding context
                start = max(0, idx - 100)
                end = min(len(content), idx + max_chars)
                return content[start:end].strip()

        # If no explicit error marker, check for convergence messages
        convergence_patterns = [
            r"CONVERGENCE NOT ACHIEVED",
            r"SOLUTION DID NOT CONVERGE",
            r"DIVERGENCE DETECTED"
        ]

        for pattern in convergence_patterns:
            match = re.search(pattern, content, re.IGNORECASE)
            if match:
                idx = match.start()
                start = max(0, idx - 50)
                end = min(len(content), idx + max_chars)
                return content[start:end].strip()

        return ""

    except Exception as e:
        return f"Error reading F06: {str(e)}"


def find_f06_file(working_dir: str, sim_file: str = "") -> Optional[Path]:
    """
    Find the F06 file in the working directory.

    Args:
        working_dir: Working directory path
        sim_file: Simulation file name (for naming pattern)

    Returns:
        Path to F06 file or None
    """
    work_path = Path(working_dir)

    # Try common patterns
    patterns = [
        "*.f06",
        "*-solution*.f06",
        "*_sim*.f06"
    ]

    for pattern in patterns:
        matches = list(work_path.glob(pattern))
        if matches:
            # Return most recently modified
            return max(matches, key=lambda p: p.stat().st_mtime)

    return None


def track_error(context: Dict[str, Any]) -> Dict[str, Any]:
    """
    Hook that preserves errors for context learning.

    Called at post_solve after solver completes.
    Captures error information regardless of success/failure
    to enable learning from both outcomes.

    Args:
        context: Hook context with trial information

    Returns:
        Dictionary with error tracking results
    """
    trial_number = context.get('trial_number', -1)
    working_dir = context.get('working_dir', '.')
    output_dir = context.get('output_dir', working_dir)
    solver_returncode = context.get('solver_returncode', 0)

    # Determine if this is an error case
    # (solver returncode non-zero, or explicit error flag)
    is_error = (
        solver_returncode != 0 or
        context.get('error', False) or
        context.get('solver_failed', False)
    )

    if not is_error:
        # No error to track, but still record success for learning
        return {"error_tracked": False, "trial_success": True}

    # Find and extract F06 error info
    f06_path = context.get('f06_path')
    if not f06_path:
        f06_file = find_f06_file(working_dir, context.get('sim_file', ''))
        if f06_file:
            f06_path = str(f06_file)

    f06_snippet = extract_f06_error(f06_path)

    # Get error message from context or F06
    error_message = context.get('error_message', '')
    if not error_message and f06_snippet:
        # Extract first line of F06 error as message
        lines = f06_snippet.strip().split('\n')
        error_message = lines[0][:200] if lines else "Unknown solver error"

    # Classify error
    error_type = classify_error(error_message or f06_snippet)

    # Build error record
    error_info = {
        "trial": trial_number,
        "timestamp": datetime.now().isoformat(),
        "solver_returncode": solver_returncode,
        "error_type": error_type,
        "error_message": error_message,
        "f06_snippet": f06_snippet[:1000] if f06_snippet else "",
        "design_variables": context.get('design_variables', {}),
        "working_dir": working_dir
    }

    # Save to error log (append mode - accumulate errors)
    error_log_path = Path(output_dir) / "error_history.jsonl"
    try:
        error_log_path.parent.mkdir(parents=True, exist_ok=True)
        with open(error_log_path, 'a', encoding='utf-8') as f:
            f.write(json.dumps(error_info) + "\n")
    except Exception as e:
        print(f"Warning: Could not write error log: {e}")

    # Try to update session state if context engineering is active
    try:
        from optimization_engine.context.session_state import get_session
        session = get_session()
        session.add_error(
            f"Trial {trial_number}: {error_type} - {error_message[:100]}",
            error_type=error_type
        )
    except ImportError:
        pass  # Context module not available

    # Try to record to LAC if available
    try:
        from knowledge_base.lac import get_lac
        lac = get_lac()
        lac.record_insight(
            category="failure",
            context=f"Trial {trial_number} solver error",
            insight=f"{error_type}: {error_message[:200]}",
            confidence=0.7,
            tags=["solver", error_type, "automatic"]
        )
    except ImportError:
        pass  # LAC not available

    return {
        "error_tracked": True,
        "error_type": error_type,
        "error_message": error_message[:200],
        "f06_extracted": bool(f06_snippet)
    }


# Hook registration metadata
HOOK_CONFIG = {
    "name": "error_tracker",
    "hook_point": "post_solve",
    "priority": 100,  # Run early to capture before cleanup
    "enabled": True,
    "description": "Preserves solver errors for context learning"
}


# Make the function discoverable by hook manager
def get_hook():
    """Return the hook function for registration."""
    return track_error


# For direct plugin discovery
__all__ = ['track_error', 'HOOK_CONFIG', 'get_hook']
