# createsonline/ai/query.py
"""
Smart query engine and natural language query processing for CREATESONLINE.

Provides AI-powered query interfaces for database models.
"""

import re
import time
from typing import Any, Dict, List, Optional, Tuple, Union


class SmartQueryEngine:
    """
    AI-powered query engine that translates natural language into database queries.
    
    Usage::
    
        engine = SmartQueryEngine(db_session)
        results = engine.query("Find all users who signed up last month")
    """
    
    def __init__(self, db_session=None, models: Dict[str, Any] = None):
        self.db_session = db_session
        self.models = models or {}
        self._query_cache: Dict[str, Any] = {}
        self._patterns = self._build_patterns()
    
    def register_model(self, name: str, model_class):
        """Register a SQLAlchemy model for querying."""
        self.models[name.lower()] = model_class
    
    def _build_patterns(self) -> List[Tuple[str, str]]:
        """Build NL → SQL pattern mappings."""
        return [
            (r'(?:find|get|show|list)\s+all\s+(\w+)', 'select_all'),
            (r'(?:find|get|show)\s+(\w+)\s+where\s+(\w+)\s*(?:=|is|equals?)\s*(.+)', 'filter_eq'),
            (r'(?:find|get|show)\s+(\w+)\s+(?:with|having)\s+(\w+)\s+(?:greater|more|above|>)\s*(?:than)?\s*(\S+)', 'filter_gt'),
            (r'(?:find|get|show)\s+(\w+)\s+(?:with|having)\s+(\w+)\s+(?:less|fewer|below|<)\s*(?:than)?\s*(\S+)', 'filter_lt'),
            (r'(?:count|how many)\s+(\w+)', 'count'),
            (r'(?:find|get|show)\s+(?:latest|newest|recent)\s+(\d+)?\s*(\w+)', 'latest'),
            (r'(?:find|get|show)\s+(\w+)\s+(?:containing|like|matching)\s+(.+)', 'filter_like'),
            (r'(?:search|find)\s+(?:for\s+)?["\'](.+?)["\']\s+in\s+(\w+)', 'search_text'),
            (r'(?:delete|remove)\s+(\w+)\s+where\s+(\w+)\s*(?:=|is)\s*(.+)', 'delete'),
            (r'(?:update|set)\s+(\w+)\s+set\s+(\w+)\s*=\s*(.+)\s+where\s+(\w+)\s*=\s*(.+)', 'update'),
        ]
    
    def query(self, natural_language: str, limit: int = 100) -> dict:
        """
        Process a natural language query.
        
        Returns dict with: 'results', 'query_type', 'parsed', 'count'.
        """
        nl_lower = natural_language.lower().strip()
        
        # Check cache
        cache_key = f"{nl_lower}:{limit}"
        if cache_key in self._query_cache:
            return self._query_cache[cache_key]
        
        # Try pattern matching
        for pattern, query_type in self._patterns:
            match = re.search(pattern, nl_lower)
            if match:
                result = self._execute_pattern(query_type, match, limit)
                if result:
                    self._query_cache[cache_key] = result
                    return result
        
        return {
            'results': [],
            'query_type': 'unknown',
            'parsed': {'raw': natural_language},
            'count': 0,
            'error': 'Could not parse query. Try: "find all users" or "get user where name = John"',
        }
    
    def _execute_pattern(self, query_type: str, match, limit: int) -> Optional[dict]:
        """Execute a matched query pattern."""
        if not self.db_session:
            return {
                'results': [],
                'query_type': query_type,
                'parsed': {'groups': match.groups()},
                'count': 0,
                'error': 'No database session configured',
            }
        
        groups = match.groups()
        
        try:
            if query_type == 'select_all':
                model_name = groups[0].rstrip('s')  # depluralize
                model = self._resolve_model(model_name)
                if model is None:
                    return {'results': [], 'query_type': query_type, 'count': 0,
                            'error': f'Model "{model_name}" not found'}
                results = self.db_session.query(model).limit(limit).all()
                return {
                    'results': results,
                    'query_type': query_type,
                    'parsed': {'model': model_name},
                    'count': len(results),
                }
            
            elif query_type == 'count':
                model_name = groups[0].rstrip('s')
                model = self._resolve_model(model_name)
                if model is None:
                    return {'results': [], 'query_type': query_type, 'count': 0,
                            'error': f'Model "{model_name}" not found'}
                count = self.db_session.query(model).count()
                return {
                    'results': [{'count': count}],
                    'query_type': query_type,
                    'parsed': {'model': model_name},
                    'count': count,
                }
            
            elif query_type in ('filter_eq', 'filter_gt', 'filter_lt', 'filter_like'):
                model_name = groups[0].rstrip('s')
                field_name = groups[1]
                value = groups[2].strip().strip('"\'')
                
                model = self._resolve_model(model_name)
                if model is None:
                    return {'results': [], 'query_type': query_type, 'count': 0,
                            'error': f'Model "{model_name}" not found'}
                
                field = getattr(model, field_name, None)
                if field is None:
                    return {'results': [], 'query_type': query_type, 'count': 0,
                            'error': f'Field "{field_name}" not found on model "{model_name}"'}
                
                q = self.db_session.query(model)
                if query_type == 'filter_eq':
                    q = q.filter(field == value)
                elif query_type == 'filter_gt':
                    q = q.filter(field > float(value))
                elif query_type == 'filter_lt':
                    q = q.filter(field < float(value))
                elif query_type == 'filter_like':
                    q = q.filter(field.ilike(f'%{value}%'))
                
                results = q.limit(limit).all()
                return {
                    'results': results,
                    'query_type': query_type,
                    'parsed': {'model': model_name, 'field': field_name, 'value': value},
                    'count': len(results),
                }
        except Exception as e:
            return {
                'results': [],
                'query_type': query_type,
                'parsed': {'groups': groups},
                'count': 0,
                'error': str(e),
            }
        
        return None
    
    def _resolve_model(self, name: str):
        """Resolve a model name to a model class."""
        name_lower = name.lower()
        if name_lower in self.models:
            return self.models[name_lower]
        # Try plural
        if name_lower + 's' in self.models:
            return self.models[name_lower + 's']
        # Try singular
        if name_lower.rstrip('s') in self.models:
            return self.models[name_lower.rstrip('s')]
        return None
    
    def clear_cache(self):
        self._query_cache.clear()
    
    def __repr__(self):
        return f"SmartQueryEngine(models={list(self.models.keys())})"


class NaturalLanguageQuery:
    """
    Lightweight NL query parser that extracts structured intent.
    
    Usage::
    
        nlq = NaturalLanguageQuery()
        parsed = nlq.parse("Show me users with age greater than 25 sorted by name")
        # {'action': 'select', 'entity': 'users', 'filters': [{'field': 'age', 'op': '>', 'value': '25'}],
        #  'sort': [{'field': 'name', 'direction': 'asc'}]}
    """
    
    ACTION_KEYWORDS = {
        'find': 'select', 'get': 'select', 'show': 'select', 'list': 'select',
        'fetch': 'select', 'retrieve': 'select', 'display': 'select',
        'count': 'count', 'how many': 'count',
        'create': 'insert', 'add': 'insert', 'insert': 'insert', 'new': 'insert',
        'update': 'update', 'set': 'update', 'change': 'update', 'modify': 'update',
        'delete': 'delete', 'remove': 'delete', 'drop': 'delete',
    }
    
    def parse(self, query: str) -> dict:
        """Parse a natural language query into structured form."""
        result = {
            'raw': query,
            'action': 'select',
            'entity': None,
            'filters': [],
            'sort': [],
            'limit': None,
            'fields': [],
        }
        
        q_lower = query.lower().strip()
        words = q_lower.split()
        
        # Detect action
        for keyword, action in self.ACTION_KEYWORDS.items():
            if q_lower.startswith(keyword) or f' {keyword} ' in f' {q_lower} ':
                result['action'] = action
                break
        
        # Detect entity (first noun-like word after action)
        skip_words = {'me', 'all', 'the', 'a', 'an', 'of', 'for', 'with', 'from',
                       'in', 'on', 'to', 'and', 'or', 'that', 'which', 'where'}
        skip_words.update(self.ACTION_KEYWORDS.keys())
        for word in words:
            if word not in skip_words and len(word) > 1:
                result['entity'] = word
                break
        
        # Detect filters
        filter_patterns = [
            (r'(\w+)\s*(?:=|is|equals?)\s*["\']?(\S+?)["\']?(?:\s|$)', '='),
            (r'(\w+)\s*(?:greater|more|above|>)\s*(?:than\s*)?(\S+)', '>'),
            (r'(\w+)\s*(?:less|fewer|below|<)\s*(?:than\s*)?(\S+)', '<'),
            (r'(\w+)\s*(?:contains?|like|matching)\s*["\']?(.+?)["\']?(?:\s|$)', 'like'),
            (r'(\w+)\s*between\s*(\S+)\s*and\s*(\S+)', 'between'),
        ]
        
        for pattern, op in filter_patterns:
            for match in re.finditer(pattern, q_lower):
                groups = match.groups()
                field = groups[0]
                if field in skip_words or field == result.get('entity'):
                    continue
                filt = {'field': field, 'op': op, 'value': groups[1]}
                if op == 'between' and len(groups) > 2:
                    filt['value'] = (groups[1], groups[2])
                result['filters'].append(filt)
        
        # Detect sort
        sort_match = re.search(
            r'(?:sort|order)\s*(?:by|ed\s+by)?\s+(\w+)\s*(asc|desc|ascending|descending)?',
            q_lower
        )
        if sort_match:
            direction = 'desc' if sort_match.group(2) and sort_match.group(2).startswith('desc') else 'asc'
            result['sort'].append({'field': sort_match.group(1), 'direction': direction})
        
        # Detect limit
        limit_match = re.search(r'(?:limit|top|first|last)\s*(\d+)', q_lower)
        if limit_match:
            result['limit'] = int(limit_match.group(1))
        
        return result
    
    def __repr__(self):
        return "NaturalLanguageQuery()"


class AIQueryProcessor:
    """
    Combines SmartQueryEngine and NaturalLanguageQuery with caching
    and query history tracking.
    """
    
    def __init__(self, db_session=None):
        self.engine = SmartQueryEngine(db_session)
        self.nlq = NaturalLanguageQuery()
        self._history: List[dict] = []
    
    def process(self, query: str, **kwargs) -> dict:
        """Process a natural language query end-to-end."""
        parsed = self.nlq.parse(query)
        result = self.engine.query(query, **kwargs)
        result['parsed_intent'] = parsed
        
        self._history.append({
            'query': query,
            'parsed': parsed,
            'result_count': result.get('count', 0),
            'timestamp': time.time(),
        })
        
        return result
    
    def register_model(self, name: str, model_class):
        self.engine.register_model(name, model_class)
    
    @property
    def history(self) -> List[dict]:
        return list(self._history)
    
    def __repr__(self):
        return f"AIQueryProcessor(queries={len(self._history)})"
