"""
Validation utilities for Toxo platform.

This module provides validation functions for configuration, training data,
and other inputs to ensure data quality and system reliability.
"""

import re
import json
from pathlib import Path
from typing import Dict, Any, List, Optional, Union, Tuple

try:
    import pandas as pd
    PANDAS_AVAILABLE = True
except ImportError:
    pd = None
    PANDAS_AVAILABLE = False
try:
    import jsonschema
    JSONSCHEMA_AVAILABLE = True
except ImportError:
    jsonschema = None
    JSONSCHEMA_AVAILABLE = False

from pydantic import BaseModel, Field, validator

from .exceptions import ValidationError


class TrainingExample(BaseModel):
    """Validation model for training examples."""
    input: str = Field(..., min_length=1, description="Input text")
    output: str = Field(..., min_length=1, description="Expected output text")
    metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional metadata")
    
    @validator('input')
    def validate_input(cls, v):
        if not v.strip():
            raise ValueError("Input cannot be empty or whitespace only")
        if len(v) > 10000:  # 10k character limit
            raise ValueError("Input text too long (max 10,000 characters)")
        return v.strip()
    
    @validator('output')
    def validate_output(cls, v):
        if not v.strip():
            raise ValueError("Output cannot be empty or whitespace only")
        if len(v) > 10000:  # 10k character limit
            raise ValueError("Output text too long (max 10,000 characters)")
        return v.strip()


class FeedbackExample(BaseModel):
    """Validation model for feedback examples."""
    input: str = Field(..., min_length=1)
    expected_output: str = Field(..., min_length=1)
    actual_output: Optional[str] = None
    feedback_type: str = Field(..., pattern="^(correction|rating|preference)$")
    rating: Optional[float] = Field(ge=0.0, le=1.0)
    metadata: Optional[Dict[str, Any]] = Field(default_factory=dict)


def validate_config(config_dict: Dict[str, Any]) -> List[str]:
    """
    Validate configuration dictionary.
    
    Args:
        config_dict: Configuration dictionary to validate
        
    Returns:
        List of validation error messages (empty if valid)
    """
    errors = []
    
    # Check required sections
    required_sections = ['gemini', 'training', 'memory', 'prompt']
    for section in required_sections:
        if section not in config_dict:
            errors.append(f"Missing required configuration section: {section}")
            continue
        
        # Validate individual sections
        section_errors = _validate_config_section(section, config_dict[section])
        errors.extend(section_errors)
    
    return errors


def _validate_config_section(section: str, config: Dict[str, Any]) -> List[str]:
    """Validate individual configuration section."""
    errors = []
    
    if section == 'gemini':
        if 'model' not in config:
            errors.append("Gemini model not specified")
        elif not isinstance(config['model'], str):
            errors.append("Gemini model must be a string")
        
        if 'temperature' in config:
            temp = config['temperature']
            if not isinstance(temp, (int, float)) or not 0 <= temp <= 2:
                errors.append("Temperature must be between 0 and 2")
    
    elif section == 'training':
        if 'learning_rate' in config:
            lr = config['learning_rate']
            if not isinstance(lr, (int, float)) or not 0 < lr < 1:
                errors.append("Learning rate must be between 0 and 1")
        
        if 'batch_size' in config:
            bs = config['batch_size']
            if not isinstance(bs, int) or bs < 1:
                errors.append("Batch size must be a positive integer")
    
    elif section == 'memory':
        if 'embedding_dimension' in config:
            dim = config['embedding_dimension']
            valid_dims = [384, 512, 768, 1024, 1536]
            if dim not in valid_dims:
                errors.append(f"Embedding dimension must be one of: {valid_dims}")
    
    elif section == 'prompt':
        if 'soft_prompt_length' in config:
            length = config['soft_prompt_length']
            if not isinstance(length, int) or not 1 <= length <= 200:
                errors.append("Soft prompt length must be between 1 and 200")
    
    return errors


def validate_training_data(
    data: Union[str, Path, List[Dict], "Any"],
    format: str = "auto"
) -> Tuple[bool, List[str], List[TrainingExample]]:
    """
    Validate training data in various formats.
    
    Args:
        data: Training data (file path, list of dicts, or DataFrame)
        format: Data format ('auto', 'jsonl', 'csv', 'json')
        
    Returns:
        Tuple of (is_valid, error_messages, validated_examples)
    """
    errors = []
    examples = []
    
    try:
        # Convert data to standard format
        if isinstance(data, (str, Path)):
            data_path = Path(data)
            if not data_path.exists():
                return False, [f"Data file not found: {data_path}"], []
            
            # Auto-detect format
            if format == "auto":
                if data_path.suffix == '.jsonl':
                    format = "jsonl"
                elif data_path.suffix == '.csv':
                    format = "csv"
                elif data_path.suffix == '.json':
                    format = "json"
                else:
                    errors.append(f"Cannot auto-detect format for {data_path.suffix}")
                    return False, errors, []
            
            # Load data based on format
            raw_data = _load_data_file(data_path, format)
            
        elif PANDAS_AVAILABLE and isinstance(data, pd.DataFrame):
            raw_data = data.to_dict('records')
        elif isinstance(data, list):
            raw_data = data
        else:
            errors.append(f"Unsupported data type: {type(data)}")
            return False, errors, []
        
        # Validate individual examples
        for i, example_data in enumerate(raw_data):
            try:
                # Ensure required fields exist
                if not isinstance(example_data, dict):
                    errors.append(f"Example {i}: Must be a dictionary")
                    continue
                
                # Normalize field names (handle variations)
                normalized_data = _normalize_example_fields(example_data)
                
                # Validate using Pydantic model
                example = TrainingExample(**normalized_data)
                examples.append(example)
                
            except Exception as e:
                errors.append(f"Example {i}: {str(e)}")
        
        # Check minimum data requirements
        if len(examples) < 5:
            errors.append("Training data must contain at least 5 examples")
        
        # Check for duplicates
        duplicate_count = _check_duplicates(examples)
        if duplicate_count > len(examples) * 0.5:  # More than 50% duplicates
            errors.append(f"Too many duplicate examples: {duplicate_count}/{len(examples)}")
        
        # Validate diversity
        diversity_score = _calculate_diversity(examples)
        if diversity_score < 0.3:  # Low diversity threshold
            errors.append(f"Training data lacks diversity (score: {diversity_score:.2f})")
        
        is_valid = len(errors) == 0
        return is_valid, errors, examples
        
    except Exception as e:
        errors.append(f"Data validation failed: {str(e)}")
        return False, errors, []


def _load_data_file(data_path: Path, format: str) -> List[Dict]:
    """Load data from file based on format."""
    if format == "jsonl":
        data = []
        with open(data_path, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                try:
                    data.append(json.loads(line.strip()))
                except json.JSONDecodeError as e:
                    raise ValidationError(f"Invalid JSON on line {line_num}: {str(e)}")
        return data
    
    elif format == "csv":
        if not PANDAS_AVAILABLE:
            raise ImportError("pandas is required for CSV validation. Install with: pip install pandas")
        df = pd.read_csv(data_path)
        return df.to_dict('records')
    
    elif format == "json":
        with open(data_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        if not isinstance(data, list):
            raise ValidationError("JSON file must contain a list of examples")
        return data
    
    else:
        raise ValidationError(f"Unsupported format: {format}")


def _normalize_example_fields(example_data: Dict[str, Any]) -> Dict[str, Any]:
    """Normalize field names in example data."""
    # Common field name variations
    input_fields = ['input', 'prompt', 'question', 'text', 'query']
    output_fields = ['output', 'response', 'answer', 'target', 'label']
    
    normalized = {}
    
    # Find input field
    for field in input_fields:
        if field in example_data:
            normalized['input'] = example_data[field]
            break
    
    # Find output field
    for field in output_fields:
        if field in example_data:
            normalized['output'] = example_data[field]
            break
    
    # Add metadata for any other fields
    metadata = {}
    for key, value in example_data.items():
        if key not in input_fields + output_fields:
            metadata[key] = value
    
    if metadata:
        normalized['metadata'] = metadata
    
    return normalized


def _check_duplicates(examples: List[TrainingExample]) -> int:
    """Check for duplicate examples."""
    seen = set()
    duplicates = 0
    
    for example in examples:
        # Create a simple hash of input+output
        example_hash = hash(example.input + example.output)
        if example_hash in seen:
            duplicates += 1
        else:
            seen.add(example_hash)
    
    return duplicates


def _calculate_diversity(examples: List[TrainingExample]) -> float:
    """Calculate diversity score for training examples."""
    if len(examples) < 2:
        return 0.0
    
    # Simple diversity metric based on unique words
    all_words = set()
    total_words = 0
    
    for example in examples:
        words = set((example.input + " " + example.output).lower().split())
        all_words.update(words)
        total_words += len(words)
    
    # Diversity score: unique words / total words
    if total_words == 0:
        return 0.0
    
    return len(all_words) / total_words


def validate_feedback_data(
    feedback_data: List[Dict[str, Any]]
) -> Tuple[bool, List[str], List[FeedbackExample]]:
    """
    Validate feedback data.
    
    Args:
        feedback_data: List of feedback examples
        
    Returns:
        Tuple of (is_valid, error_messages, validated_examples)
    """
    errors = []
    examples = []
    
    for i, feedback_item in enumerate(feedback_data):
        try:
            example = FeedbackExample(**feedback_item)
            examples.append(example)
        except Exception as e:
            errors.append(f"Feedback {i}: {str(e)}")
    
    is_valid = len(errors) == 0
    return is_valid, errors, examples


def validate_domain_name(domain: str) -> bool:
    """
    Validate domain name format.
    
    Args:
        domain: Domain name to validate
        
    Returns:
        True if valid, False otherwise
    """
    # Domain name should be lowercase, alphanumeric with underscores/hyphens
    pattern = r'^[a-z0-9_-]+$'
    return bool(re.match(pattern, domain)) and 3 <= len(domain) <= 50


def validate_model_name(name: str) -> bool:
    """
    Validate model/layer name format.
    
    Args:
        name: Model name to validate
        
    Returns:
        True if valid, False otherwise
    """
    # Model name should be alphanumeric with underscores/hyphens
    pattern = r'^[a-zA-Z0-9_-]+$'
    return bool(re.match(pattern, name)) and 3 <= len(name) <= 100


def validate_api_key(api_key: str, provider: str = "gemini") -> bool:
    """
    Validate API key format.
    
    Args:
        api_key: API key to validate
        provider: API provider ('gemini', 'openai', etc.)
        
    Returns:
        True if valid format, False otherwise
    """
    if not api_key or not isinstance(api_key, str):
        return False
    
    if provider == "gemini":
        # Gemini API keys typically start with specific patterns
        return api_key.startswith(('AIza', 'AI')) and len(api_key) >= 20
    elif provider == "openai":
        return api_key.startswith('sk-') and len(api_key) >= 20
    else:
        # Generic validation - at least 20 characters
        return len(api_key) >= 20


def validate_json_schema(data: Dict[str, Any], schema: Dict[str, Any]) -> List[str]:
    """
    Validate data against JSON schema.
    
    Args:
        data: Data to validate
        schema: JSON schema
        
    Returns:
        List of validation error messages
    """
    errors = []
    
    try:
        if not JSONSCHEMA_AVAILABLE:
            return []  # Skip validation if jsonschema not installed
        jsonschema.validate(instance=data, schema=schema)
    except Exception as e:
        err_msg = getattr(e, "message", str(e))
        errors.append(f"Schema validation error: {err_msg}")
    
    return errors


# JSON schemas for common validation tasks
TRAINING_DATA_SCHEMA = {
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "input": {"type": "string", "minLength": 1},
            "output": {"type": "string", "minLength": 1},
            "metadata": {"type": "object"}
        },
        "required": ["input", "output"]
    },
    "minItems": 1
}

CONFIG_SCHEMA = {
    "type": "object",
    "properties": {
        "gemini": {
            "type": "object",
            "properties": {
                "model": {"type": "string"},
                "temperature": {"type": "number", "minimum": 0, "maximum": 2},
                "max_tokens": {"type": "integer", "minimum": 1}
            },
            "required": ["model"]
        },
        "training": {
            "type": "object",
            "properties": {
                "learning_rate": {"type": "number", "minimum": 0, "maximum": 1},
                "batch_size": {"type": "integer", "minimum": 1}
            }
        }
    },
    "required": ["gemini", "training"]
}


def quick_validate_training_file(file_path: Union[str, Path]) -> bool:
    """
    Quick validation check for training data file.
    
    Args:
        file_path: Path to training data file
        
    Returns:
        True if file appears valid, False otherwise
    """
    try:
        is_valid, errors, _ = validate_training_data(file_path)
        return is_valid and len(errors) == 0
    except:
        return False 