#!/usr/bin/env python3
"""
SQL Query Strategy

This module implements the SQL query strategy for structured data queries.

Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.6
Generation Date: January 2, 2025
"""

from __future__ import annotations

import re
from typing import Any
from .base import AStructuredQueryStrategy
from exonware.xwnode.errors import XWNodeValueError
from exonware.xwnode.contracts import QueryMode, QueryTrait
from exonware.xwnode.base import XWNodeBase

# Unquoted SQL identifiers (schema.table.column) — WHY: reject identifier-based injection in helpers.
_DOTTED_IDENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
_SIMPLE_IDENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")


def _validate_dotted_identifier(name: str, *, field: str) -> str:
    s = name.strip()
    if not _DOTTED_IDENT.match(s):
        raise XWNodeValueError(
            f"Invalid SQL {field}: expected unquoted identifier(s), got {name!r}"
        )
    return s


def _validate_column_ref(col: str) -> str:
    c = col.strip()
    if c == "*":
        return c
    if c.endswith(".*"):
        base = c[:-2].strip()
        if _DOTTED_IDENT.match(base):
            return c
        raise XWNodeValueError(f"Invalid SQL column reference: {col!r}")
    if _DOTTED_IDENT.match(c):
        return c
    raise XWNodeValueError(f"Invalid SQL column reference: {col!r}")


def _reject_unsafe_sql_fragment(fragment: str, *, field: str) -> None:
    """Block obvious multi-statement / comment injection in concatenated SQL fragments."""
    if not fragment:
        return
    if any(bad in fragment for bad in (";", "--", "/*", "*/", "\x00")):
        raise XWNodeValueError(
            f"Unsafe SQL {field}: cannot contain statement terminators or SQL comments; "
            "pass a single predicate fragment or use bound parameters at the database layer."
        )


def _validate_aggregate_expr(expr: str) -> str:
    s = expr.strip()
    _reject_unsafe_sql_fragment(s, field="aggregate expression")
    return s


class SQLStrategy(AStructuredQueryStrategy):
    """
    SQL query strategy for standard SQL operations.
    
    Supports:
    - SELECT, INSERT, UPDATE, DELETE operations
    - JOIN operations
    - Aggregate functions
    - WHERE clauses
    - ORDER BY, GROUP BY, HAVING
    """
    
    def __init__(self, **options):
        super().__init__(**options)
        self._mode = QueryMode.SQL
        self._traits = QueryTrait.STRUCTURED | QueryTrait.ANALYTICAL | QueryTrait.BATCH
    
    def execute(self, query: str, **kwargs) -> Any:
        """Execute SQL query."""
        if not self.validate_query(query):
            raise XWNodeValueError(f"Invalid SQL query: {query}")
        
        query_type = self._get_query_type(query)
        
        if query_type == "SELECT":
            return self._execute_select(query, **kwargs)
        elif query_type == "INSERT":
            return self._execute_insert(query, **kwargs)
        elif query_type == "UPDATE":
            return self._execute_update(query, **kwargs)
        elif query_type == "DELETE":
            return self._execute_delete(query, **kwargs)
        else:
            raise XWNodeValueError(f"Unsupported query type: {query_type}")
    
    def validate_query(self, query: str) -> bool:
        """Validate SQL query syntax."""
        if not query or not isinstance(query, str):
            return False
        
        # Basic SQL validation
        query = query.strip().upper()
        valid_operations = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER"]
        
        for operation in valid_operations:
            if query.startswith(operation):
                return True
        
        return False
    
    def get_query_plan(self, query: str) -> dict[str, Any]:
        """Get SQL query execution plan."""
        query_type = self._get_query_type(query)
        
        return {
            "query_type": query_type,
            "operation": query_type,
            "complexity": self._estimate_complexity(query),
            "estimated_cost": self._estimate_cost(query),
            "optimization_hints": self._get_optimization_hints(query)
        }
    
    def select_query(self, table: str, columns: list[str], where_clause: str = None) -> Any:
        """Execute SELECT built from identifiers.

        Security: *table* and *columns* must be unquoted identifiers (or ``*`` / ``schema.table.*``).
        *where_clause* is a single predicate fragment; multi-statement and SQL comments are rejected.
        For untrusted input, use bound parameters at your database driver, not string concatenation here.
        """
        t = _validate_dotted_identifier(table, field="table")
        safe_cols = [_validate_column_ref(c) for c in columns]
        if where_clause is not None:
            _reject_unsafe_sql_fragment(where_clause, field="where_clause")
        query = f"SELECT {', '.join(safe_cols)} FROM {t}"
        if where_clause:
            query += f" WHERE {where_clause.strip()}"
        
        return self.execute(query)
    
    def insert_query(self, table: str, data: dict[str, Any]) -> Any:
        """Execute INSERT query. Column names must be simple unquoted identifiers."""
        t = _validate_dotted_identifier(table, field="table")
        columns = list(data.keys())
        for k in columns:
            if not _SIMPLE_IDENT.match(k.strip()):
                raise XWNodeValueError(f"Invalid SQL column name: {k!r}")
        values = list(data.values())
        
        query = f"INSERT INTO {t} ({', '.join(columns)}) VALUES ({', '.join(['?' for _ in values])})"
        return self.execute(query, values=values)
    
    def update_query(self, table: str, data: dict[str, Any], where_clause: str = None) -> Any:
        """Execute UPDATE query."""
        t = _validate_dotted_identifier(table, field="table")
        for k in data.keys():
            if not _SIMPLE_IDENT.match(k.strip()):
                raise XWNodeValueError(f"Invalid SQL column name: {k!r}")
        if where_clause is not None:
            _reject_unsafe_sql_fragment(where_clause, field="where_clause")
        set_clause = ', '.join([f"{k.strip()} = ?" for k in data.keys()])
        query = f"UPDATE {t} SET {set_clause}"
        
        if where_clause:
            query += f" WHERE {where_clause.strip()}"
        
        return self.execute(query, values=list(data.values()))
    
    def delete_query(self, table: str, where_clause: str = None) -> Any:
        """Execute DELETE query."""
        t = _validate_dotted_identifier(table, field="table")
        if where_clause is not None:
            _reject_unsafe_sql_fragment(where_clause, field="where_clause")
        query = f"DELETE FROM {t}"
        if where_clause:
            query += f" WHERE {where_clause.strip()}"
        
        return self.execute(query)
    
    def join_query(self, tables: list[str], join_conditions: list[str]) -> Any:
        """Execute JOIN query. *join_conditions* fragments are checked for comment/statement injection."""
        if len(tables) < 2:
            raise XWNodeValueError("JOIN requires at least 2 tables")
        safe_tables = [_validate_dotted_identifier(x, field="table") for x in tables]
        for jc in join_conditions:
            _reject_unsafe_sql_fragment(jc, field="join condition")
        
        query = f"SELECT * FROM {safe_tables[0]}"
        for i, table in enumerate(safe_tables[1:], 1):
            if i <= len(join_conditions):
                query += f" JOIN {table} ON {join_conditions[i-1].strip()}"
            else:
                query += f" CROSS JOIN {table}"
        
        return self.execute(query)
    
    def aggregate_query(self, table: str, functions: list[str], group_by: list[str] = None) -> Any:
        """Execute aggregate query. *functions* must not contain statement terminators or SQL comments."""
        t = _validate_dotted_identifier(table, field="table")
        safe_funcs = [_validate_aggregate_expr(f) for f in functions]
        gb = group_by or []
        safe_gb = [_validate_column_ref(c) for c in gb]
        query = f"SELECT {', '.join(safe_funcs)} FROM {t}"
        if safe_gb:
            query += f" GROUP BY {', '.join(safe_gb)}"
        
        return self.execute(query)
    
    def _get_query_type(self, query: str) -> str:
        """Extract query type from SQL query."""
        query = query.strip().upper()
        for operation in ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER"]:
            if query.startswith(operation):
                return operation
        return "UNKNOWN"
    
    def _execute_select(self, query: str, **kwargs) -> Any:
        """Execute SELECT query."""
        # Placeholder implementation
        return {"result": "SELECT executed", "query": query}
    
    def _execute_insert(self, query: str, **kwargs) -> Any:
        """Execute INSERT query."""
        # Placeholder implementation
        return {"result": "INSERT executed", "query": query}
    
    def _execute_update(self, query: str, **kwargs) -> Any:
        """Execute UPDATE query."""
        # Placeholder implementation
        return {"result": "UPDATE executed", "query": query}
    
    def _execute_delete(self, query: str, **kwargs) -> Any:
        """Execute DELETE query."""
        # Placeholder implementation
        return {"result": "DELETE executed", "query": query}
    
    def _estimate_complexity(self, query: str) -> str:
        """Estimate query complexity."""
        query = query.upper()
        if "JOIN" in query:
            return "HIGH"
        elif "GROUP BY" in query or "ORDER BY" in query:
            return "MEDIUM"
        else:
            return "LOW"
    
    def _estimate_cost(self, query: str) -> int:
        """Estimate query cost."""
        complexity = self._estimate_complexity(query)
        if complexity == "HIGH":
            return 100
        elif complexity == "MEDIUM":
            return 50
        else:
            return 10
    
    def _get_optimization_hints(self, query: str) -> list[str]:
        """Get query optimization hints."""
        hints = []
        query = query.upper()
        
        if "SELECT *" in query:
            hints.append("Consider specifying columns instead of using *")
        if "WHERE" not in query and "SELECT" in query:
            hints.append("Consider adding WHERE clause to limit results")
        if "ORDER BY" in query:
            hints.append("Consider adding index on ORDER BY columns")
        
        return hints
    
    def to_actions_tree(self, sql_query: str) -> XWNodeBase:
        """Convert SQL query to XWQuery Script actions tree."""
        from .xwquery import XWQueryScriptStrategy
        
        # Parse SQL into XWQuery Script actions
        XWQueryScriptStrategy()
        
        # Map SQL constructs to XWQuery actions:
        # SELECT -> SELECT action
        # FROM -> FROM action  
        # WHERE -> WHERE action
        # JOIN -> JOIN action (with nested conditions)
        # Subqueries -> nested SELECT actions
        # etc.
        
        # For now, create a basic actions tree
        actions = {
            "root": {
                "type": "PROGRAM",
                "statements": [
                    {
                        "type": "SELECT",
                        "id": "sql_action_1",
                        "content": sql_query,
                        "line_number": 1,
                        "timestamp": "2025-01-02T00:00:00",
                        "children": []
                    }
                ],
                "comments": [],
                "metadata": {
                    "version": "1.0",
                    "created": "2025-01-02T00:00:00",
                    "source_format": "SQL"
                }
            }
        }
        
        return XWNodeBase.from_native(actions)
    
    def from_actions_tree(self, actions_tree: XWNodeBase) -> str:
        """Convert XWQuery Script actions tree to SQL query."""
        from .xwquery import XWQueryScriptStrategy
        
        script_strategy = XWQueryScriptStrategy(actions_tree)
        return script_strategy.to_format("SQL")
    
    def can_handle(self, query_string: str) -> bool:
        """Check if this strategy can handle the given query string."""
        return self.validate_query(query_string)
    
    def get_supported_operations(self) -> list[str]:
        """Get list of supported query operations."""
        return [
            "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER",
            "JOIN", "UNION", "GROUP BY", "ORDER BY", "HAVING", "WHERE",
            "WITH", "CASE", "WINDOW", "PARTITION BY", "OVER"
        ]
    
    def estimate_complexity(self, query_string: str) -> dict[str, Any]:
        """Estimate query complexity and resource requirements."""
        complexity = self._estimate_complexity(query_string)
        cost = self._estimate_cost(query_string)
        
        return {
            "complexity_level": complexity,
            "estimated_cost": cost,
            "has_joins": "JOIN" in query_string.upper(),
            "has_subqueries": "SELECT" in query_string.upper().replace("SELECT", "", 1),
            "has_aggregates": any(func in query_string.upper() for func in ["SUM", "COUNT", "AVG", "MIN", "MAX"]),
            "has_window_functions": "OVER" in query_string.upper(),
            "query_length": len(query_string),
            "estimated_memory": "high" if complexity == "HIGH" else "medium" if complexity == "MEDIUM" else "low"
        }