"""Validation logic for task outputs."""

import re
import subprocess
from pathlib import Path
from typing import Optional, Tuple


class ValidationError(Exception):
    """Raised when validation fails."""
    pass


def normalize_text(text: str) -> str:
    """Normalize text by stripping whitespace and newlines."""
    if text is None:
        return ""
    # Remove leading/trailing whitespace and normalize internal whitespace
    return re.sub(r'\s+', ' ', text.strip())


def validate_exact(output: str, expected: str) -> Tuple[bool, Optional[float]]:
    """Validate output using exact string matching.
    
    Args:
        output: The actual output from the model
        expected: The expected output
        
    Returns:
        Tuple of (is_correct, None)
    """
    normalized_output = normalize_text(output)
    normalized_expected = normalize_text(expected)
    return normalized_output == normalized_expected, None


def validate_numeric(output: str, expected: str) -> Tuple[bool, Optional[float]]:
    """Validate output using numeric comparison.
    
    Extracts numeric values from both outputs and compares them.
    
    Args:
        output: The actual output from the model
        expected: The expected output
        
    Returns:
        Tuple of (is_correct, difference)
    """
    def extract_number(text: str) -> Optional[float]:
        # Find all numbers in the text (including scientific notation)
        numbers = re.findall(r'-?\d+\.?\d*(?:[eE][+-]?\d+)?', text)
        if numbers:
            try:
                return float(numbers[0])
            except ValueError:
                return None
        return None
    
    output_num = extract_number(output)
    expected_num = extract_number(expected)
    
    if output_num is None or expected_num is None:
        return False, None
    
    diff = abs(output_num - expected_num)
    # Consider correct if difference is very small (1e-10 relative tolerance)
    is_correct = diff < 1e-10 or (expected_num != 0 and diff / abs(expected_num) < 1e-10)
    
    return is_correct, diff


def validate_semantic(output: str, expected: str, yaml_file: str) -> Tuple[bool, Optional[float]]:
    """Validate output using semantic comparison.
    
    First tries exact match, then falls back to calling 'agata compare'.
    
    Args:
        output: The actual output from the model
        expected: The expected output
        yaml_file: Path to the original YAML task file
        
    Returns:
        Tuple of (is_correct, None)
    """
    # First try exact match
    exact_match, _ = validate_exact(output, expected)
    if exact_match:
        return True, None
    
    # Fall back to agata compare
    try:
        result = subprocess.run(
            ['agata', 'compare', '-f', output, '-e', yaml_file],
            capture_output=True,
            text=True,
            timeout=30
        )
        # If return code is 0, consider it correct
        return result.returncode == 0, None
    except subprocess.TimeoutExpired:
        return False, None
    except FileNotFoundError:
        # agata command not found
        raise ValidationError("agata command not found. Please install agata for semantic validation.")
    except Exception:
        return False, None


def validate_output(output: str, expected: str, validation_type: str, yaml_file: str) -> Tuple[bool, Optional[float]]:
    """Validate output based on validation type.
    
    Args:
        output: The actual output from the model
        expected: The expected output
        validation_type: Type of validation ('exact', 'semantic', or 'numeric')
        yaml_file: Path to the original YAML task file (for semantic validation)
        
    Returns:
        Tuple of (is_correct, numeric_difference or None)
        
    Raises:
        ValidationError: If validation fails due to external errors
    """
    if validation_type == 'exact':
        return validate_exact(output, expected)
    elif validation_type == 'numeric':
        return validate_numeric(output, expected)
    elif validation_type == 'semantic':
        return validate_semantic(output, expected, yaml_file)
    else:
        raise ValidationError(f"Unknown validation type: {validation_type}")
