from enum import Enum
from typing import Any, Dict, List, Optional
import re

from repenseai.genai.benchmark.core.base_evaluator import BaseEvaluator
from repenseai.genai.benchmark.core.base_provider import BaseagentProvider


class MatchType(Enum):
    EXACT = "exact"                 # Correspondência exata
    NORMALIZED = "normalized"       # Normalizado (case, pontuação, espaços)
    CONTAINS = "contains"          # Contém a resposta
    SEMANTIC = "semantic"          # Similaridade semântica
    REGEX = "regex"                # Expressão regular
    CUSTOM = "custom"              # Função customizada


class SimpleOutputEvaluator(BaseEvaluator):
    def __init__(
        self, 
        name: str, 
        validation_rules: Dict[str, Any] = None,
        metrics: List[str] = None,
        match_type: MatchType = MatchType.NORMALIZED,
        similarity_threshold: float = 0.85,
        agent_provider: Optional[BaseagentProvider] = None  # Para matching semântico
    ):
        super().__init__(name)

        self.validation_rules = validation_rules or {}
        self.metrics = metrics or ["accuracy"]
        self.match_type = match_type
        self.similarity_threshold = similarity_threshold
        self.agent_provider = agent_provider
        self.valid_outputs = 0
        self.total_outputs = 0
        self.correct_outputs = 0
        self.metrics_results = {}

    def _normalize_text(self, text: str) -> str:
        """Normaliza o texto removendo pontuação extra, espaços e case"""
        text = text.lower().strip()
        text = re.sub(r'[^\w\s]', '', text)
        text = re.sub(r'\s+', ' ', text)
        return text

    async def _check_semantic_similarity(self, output: str, ground_truth: str) -> bool:
        """Verifica similaridade semântica usando agent"""
        if not self.agent_provider:
            return False
        
        prompt = (
            "Compare these two answers and determine if they have the same meaning:\n"
            f"Answer 1: {output}\n"
            f"Answer 2: {ground_truth}\n\n"
            "Respond with only 'True' if they mean the same thing, or 'False' if they don't."
        )

        response = await self.agent_provider.generate(prompt)
        return response.strip().lower() == 'true'

    async def _compare_outputs(self, output: str, ground_truth: str) -> bool:
        """Compara outputs baseado no tipo de matching configurado"""
        if not ground_truth:
            return False

        match self.match_type:
            case MatchType.EXACT:
                return output == ground_truth
            
            case MatchType.NORMALIZED:
                return self._normalize_text(output) == self._normalize_text(ground_truth)
            
            case MatchType.CONTAINS:
                return self._normalize_text(ground_truth) in self._normalize_text(output)
            
            case MatchType.SEMANTIC:
                return await self._check_semantic_similarity(output, ground_truth)
            
            case MatchType.REGEX:
                pattern = self.validation_rules.get('regex_pattern', ground_truth)
                return bool(re.match(pattern, output))
            
            case MatchType.CUSTOM:
                custom_func = self.validation_rules.get('custom_comparator')
                return custom_func(output, ground_truth) if custom_func else False
            
    def _calculate_metrics(self, predictions: List[str], ground_truth: List[str]) -> Dict[str, float]:
        metrics_results = {}
        
        for metric in self.metrics:
            if metric == "accuracy":
                correct = sum(p == g for p, g in zip(predictions, ground_truth))
                metrics_results["accuracy"] = correct / len(predictions)
            
            elif metric == "f1":
                from sklearn.metrics import f1_score
                # Assuming binary classification for simplicity
                metrics_results["f1"] = f1_score(
                    ground_truth, 
                    predictions, 
                    average='weighted',
                    zero_division=0
                )
            
            elif metric == "precision":
                from sklearn.metrics import precision_score
                metrics_results["precision"] = precision_score(
                    ground_truth, 
                    predictions, 
                    average='weighted',
                    zero_division=0
                )
            
            elif metric == "recall":
                from sklearn.metrics import recall_score
                metrics_results["recall"] = recall_score(
                    ground_truth, 
                    predictions, 
                    average='weighted',
                    zero_division=0
                )

        return metrics_results

    def _validate_output(self, output: str) -> bool:
        """
        Validates the output based on validation rules.
        
        Validation rules can include:
        - allowed_values: List of allowed values
        - min_length: Minimum length of output
        - max_length: Maximum length of output
        - regex_pattern: Regular expression pattern to match
        - required_format: Format specification (e.g., 'number', 'date', 'email')
        - custom_validator: Custom validation function
        """
        if not self.validation_rules:
            return True

        # Check if output is None or empty
        if output is None or not isinstance(output, str):
            return False

        # Check allowed values
        if 'allowed_values' in self.validation_rules:
            if output not in self.validation_rules['allowed_values']:
                return False

        # Check length constraints
        if 'min_length' in self.validation_rules:
            if len(output) < self.validation_rules['min_length']:
                return False

        if 'max_length' in self.validation_rules:
            if len(output) > self.validation_rules['max_length']:
                return False

        # Check regex pattern
        if 'regex_pattern' in self.validation_rules:
            pattern = self.validation_rules['regex_pattern']
            if not re.match(pattern, output):
                return False

        # Check required format
        if 'required_format' in self.validation_rules:
            format_type = self.validation_rules['required_format']
            
            if format_type == 'number':
                try:
                    float(output)
                except ValueError:
                    return False
            
            elif format_type == 'date':
                try:
                    from datetime import datetime
                    datetime.strptime(output, self.validation_rules.get('date_format', '%Y-%m-%d'))
                except ValueError:
                    return False
            
            elif format_type == 'email':
                email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
                if not re.match(email_pattern, output):
                    return False

        # Custom validation
        if 'custom_validator' in self.validation_rules:
            custom_validator = self.validation_rules['custom_validator']
            try:
                if not custom_validator(output):
                    return False
            except Exception:
                return False

        return True     

    async def evaluate(self, test_results: Dict[str, Any]) -> Dict[str, Any]:
        evaluation = []

        self.total_outputs = len(test_results['results'])
        self.valid_outputs = 0
        self.correct_outputs = 0
        
        predictions = []
        ground_truth = []

        for result in test_results['results']:
            output = result['output'].upper()
            ground_truth_value = result.get('ground_truth').upper()
            
            # Valida formato se houver regras
            is_valid = self._validate_output(output)

            if is_valid:
                self.valid_outputs += 1
            
            # Compara com ground truth se existir
            is_correct = False

            if ground_truth_value is not None:
                is_correct = await self._compare_outputs(output, ground_truth_value)

            predictions.append(is_correct)
            ground_truth.append(True)

            evaluation.append(
                {
                    'input_number': result['input_number'],
                    'is_valid': is_valid,
                    'output': output,
                    'ground_truth': ground_truth_value,
                    'correct': is_correct,
                    'normalized_output': self._normalize_text(output),
                    'normalized_ground_truth': self._normalize_text(ground_truth_value) if ground_truth_value else None
                }
            )

        if ground_truth:
            self.metrics_results = self._calculate_metrics(predictions, ground_truth)

        return {
            'evaluation_details': evaluation,
            'total_outputs': self.total_outputs,
            'valid_outputs': self.valid_outputs,
            'metrics': self.metrics_results,
            'match_type': self.match_type.value
        }
    
    def get_score(self) -> float:
        if self.metrics_results:
            return self.metrics_results
        return self.valid_outputs / self.total_outputs if self.total_outputs > 0 else 0.0