"""Rule-based scorers that check patterns and structure."""

import re
import json
from typing import Optional, List

from openevalkit.scorers.base import Scorer
from openevalkit.score import Score
from openevalkit.run import Run


class RegexMatch(Scorer):
    r"""
    Checks if output matches a regex pattern.
    
    Examples:
        >>> # Check if output contains a number
        >>> scorer = RegexMatch(pattern=r'\d+')
        >>> run = Run(id="1", input="test", output="Answer: 42")
        >>> score = scorer.score(run)
        >>> score.value
        True
    """
    
    name = "regex_match"
    requires_reference = False
    cacheable = False

    def __init__(self, pattern: str, flags: int = 0):
        """
        Initialize regex matcher.
        
        Args:
            pattern: Regex pattern to match
            flags: Regex flags (e.g., re.IGNORECASE)
        """
        self.pattern = pattern
        self.flags = flags
        self.compiled_pattern = re.compile(pattern, flags)
        # Update name to include pattern
        self.name = f"regex_match_{pattern[:20]}"
    
    def score(self, run: Run) -> Score:
        """
        Check if output matches pattern.
        
        Args:
            run: Run to score
            
        Returns:
            Score with True if pattern found, False otherwise
        """
        output_str = str(run.output)
        match = self.compiled_pattern.search(output_str)
        
        return Score(
            value=bool(match),
            reason=f"Pattern {'found' if match else 'not found'}",
            metadata={
                "pattern": self.pattern,
                "match_position": match.span() if match else None
            }
        )


class JSONValid(Scorer):
    """
    Validates that output is valid JSON.
    
    Examples:
        >>> scorer = JSONValid()
        >>> run = Run(id="1", input="test", output='{"key": "value"}')
        >>> score = scorer.score(run)
        >>> score.value
        True
        
        >>> run2 = Run(id="2", input="test", output='not json')
        >>> score2 = scorer.score(run2)
        >>> score2.value
        False
    """
    
    name = "json_valid"
    requires_reference = False
    cacheable = False
    
    def __init__(self, expected_schema: Optional[dict] = None):
        """
        Initialize JSON validator.
        
        Args:
            expected_schema: Optional schema to validate against (future)
        """
        self.expected_schema = expected_schema
    
    def score(self, run: Run) -> Score:
        """
        Validate JSON structure.
        
        Args:
            run: Run to score
            
        Returns:
            Score with True if valid JSON, False otherwise
        """
        output_str = str(run.output)
        
        try:
            parsed = json.loads(output_str)
            is_valid = True
            reason = "Valid JSON"
            metadata = {"type": type(parsed).__name__}
        except json.JSONDecodeError as e:
            is_valid = False
            reason = f"Invalid JSON: {str(e)}"
            metadata = {"error": str(e)}
        
        return Score(
            value=is_valid,
            reason=reason,
            metadata=metadata
        )



class ContainsKeywords(Scorer):
    """
    Checks if output contains required keywords.
    
    Examples:
        >>> scorer = ContainsKeywords(keywords=["paris", "france"])
        >>> run = Run(id="1", input="capital?", output="Paris is in France")
        >>> score = scorer.score(run)
        >>> score.value
        True
    """

    name = "contains_keywords"
    requires_reference = False
    cacheable = False

    def __init__(self, keywords: List[str], ignore_case: bool = False):
        """
        Args:
            keywords: List of keywords to check for
            ignore_case: Whether to match case
        """
        
        self.keywords = keywords
        self.ignore_case = ignore_case

        # Add keywords to name. Truncate to keep name reasonable
        keywords_str = '_'.join(self.keywords[:3])  # First 3 keywords
        if len(self.keywords) > 3:
             keywords_str += f"_and_{len(self.keywords) - 3}_more"

        self.name = f"contains_{keywords_str}"

    
    def score(self, run: Run) -> Score:
        """Check if all keywords present."""
        
        output_str = str(run.output)
        if self.ignore_case:
            output_str = output_str.lower()
            keywords = [k.lower() for k in self.keywords]
        else:
            keywords = self.keywords
        
        found = [kw for kw in keywords if kw in output_str]
        all_found = len(found) == len(keywords)
        
        return Score(
            value=all_found,
            reason=f"Found {len(found)}/{len(keywords)} keywords",
            metadata={
                "found": found,
                "missing": [kw for kw in keywords if kw not in found]
            }
        )

    





