"""
Apache Jena Adapter Module

This module provides Apache Jena integration for RDF storage and SPARQL
querying, supporting both in-memory and remote Fuseki endpoints.

Key Features:
    - Jena connection and configuration
    - SPARQL query execution
    - Model and dataset management
    - Inference and reasoning support
    - RDF serialization
    - rdflib integration with fallback

Main Classes:
    - JenaAdapter: Main Jena integration adapter

Example Usage:
    >>> from semantica.triple_store import JenaAdapter
    >>> adapter = JenaAdapter(endpoint="http://localhost:3030/ds", dataset="default")
    >>> result = adapter.add_triples(triples)
    >>> query_result = adapter.execute_sparql(sparql_query)
    >>> rdf_turtle = adapter.serialize(format="turtle")

Author: Semantica Contributors
License: MIT
"""

from typing import Any, Dict, List, Optional

from ..utils.exceptions import ValidationError, ProcessingError
from ..utils.logging import get_logger
from ..utils.progress_tracker import get_progress_tracker
from ..semantic_extract.triple_extractor import Triple

# Optional Jena imports
try:
    from rdflib import Graph, Namespace, URIRef, Literal, RDF
    from rdflib.plugins.stores.sparqlstore import SPARQLStore
    HAS_JENA_RDFLIB = True
except ImportError:
    HAS_JENA_RDFLIB = False
    Graph = None
    RDF = None


class JenaAdapter:
    """
    Apache Jena adapter for triple store operations.
    
    • Jena connection and configuration
    • SPARQL query execution
    • Model and dataset management
    • Inference and reasoning support
    • Performance optimization
    • Error handling and recovery
    """
    
    def __init__(self, **config):
        """
        Initialize Jena adapter.
        
        Args:
            **config: Configuration options:
                - endpoint: Jena Fuseki endpoint (optional)
                - dataset: Dataset name
                - enable_inference: Enable inference (default: False)
        """
        self.logger = get_logger("jena_adapter")
        self.config = config
        self.progress_tracker = get_progress_tracker()
        
        self.endpoint = config.get("endpoint")
        self.dataset = config.get("dataset", "default")
        self.enable_inference = config.get("enable_inference", False)
        
        self.graph: Optional[Graph] = None
        self._initialize_graph()
    
    def _initialize_graph(self) -> None:
        """Initialize RDF graph."""
        if HAS_JENA_RDFLIB:
            if self.endpoint:
                # Use SPARQL store for remote endpoint
                try:
                    store = SPARQLStore(query_endpoint=self.endpoint)
                    self.graph = Graph(store=store)
                except Exception as e:
                    self.logger.warning(f"Could not initialize SPARQL store: {e}")
                    self.graph = Graph()
            else:
                # Use in-memory graph
                self.graph = Graph()
        else:
            self.logger.warning("rdflib not available. Jena adapter will use basic operations.")
            self.graph = None
    
    def create_model(self, **options) -> Dict[str, Any]:
        """
        Create and manage RDF models.
        
        Args:
            **options: Model options
        
        Returns:
            Model information
        """
        if self.graph is None:
            self._initialize_graph()
        
        return {
            "model_id": self.dataset,
            "endpoint": self.endpoint,
            "triple_count": len(self.graph) if self.graph else 0
        }
    
    def add_triples(
        self,
        triples: List[Triple],
        **options
    ) -> Dict[str, Any]:
        """
        Add triples to model.
        
        Args:
            triples: List of triples
            **options: Additional options
        
        Returns:
            Operation status
        """
        tracking_id = self.progress_tracker.start_tracking(
            module="triple_store",
            submodule="JenaAdapter",
            message=f"Adding {len(triples)} triples to Jena model"
        )
        
        try:
            if not self.graph:
                self.progress_tracker.stop_tracking(tracking_id, status="failed", message="Graph not initialized")
                raise ProcessingError("Graph not initialized")
            
            added_count = 0
            self.progress_tracker.update_tracking(tracking_id, message="Adding triples to graph...")
            for triple in triples:
                try:
                    subject = URIRef(triple.subject)
                    predicate = URIRef(triple.predicate)
                    obj = URIRef(triple.object) if triple.object.startswith("http") else Literal(triple.object)
                    
                    self.graph.add((subject, predicate, obj))
                    added_count += 1
                except Exception as e:
                    self.logger.warning(f"Failed to add triple: {e}")
            
            self.progress_tracker.stop_tracking(tracking_id, status="completed",
                                              message=f"Added {added_count}/{len(triples)} triples")
            return {
                "success": True,
                "added": added_count,
                "total": len(triples)
            }
        except Exception as e:
            self.logger.error(f"Failed to add triples: {e}")
            self.progress_tracker.stop_tracking(tracking_id, status="failed", message=str(e))
            raise ProcessingError(f"Failed to add triples: {e}")
    
    def add_triple(self, triple: Triple, **options) -> Dict[str, Any]:
        """Add single triple."""
        return self.add_triples([triple], **options)
    
    def get_triples(
        self,
        subject: Optional[str] = None,
        predicate: Optional[str] = None,
        object: Optional[str] = None,
        **options
    ) -> List[Triple]:
        """Get triples matching criteria."""
        if not self.graph:
            return []
        
        try:
            # Build SPARQL query
            query_parts = []
            if subject:
                query_parts.append(f"?s = <{subject}>")
            if predicate:
                query_parts.append(f"?p = <{predicate}>")
            if object:
                query_parts.append(f"?o = <{object}>")
            
            where_clause = " ".join(query_parts) if query_parts else ""
            query = f"SELECT ?s ?p ?o WHERE {{ ?s ?p ?o {where_clause} }}"
            
            results = self.graph.query(query)
            
            triples = []
            for row in results:
                triples.append(Triple(
                    subject=str(row.s),
                    predicate=str(row.p),
                    object=str(row.o),
                    metadata={"source": "jena"}
                ))
            
            return triples
        except Exception as e:
            self.logger.error(f"Failed to get triples: {e}")
            return []
    
    def delete_triple(self, triple: Triple, **options) -> Dict[str, Any]:
        """Delete triple."""
        if not self.graph:
            raise ProcessingError("Graph not initialized")
        
        try:
            subject = URIRef(triple.subject)
            predicate = URIRef(triple.predicate)
            obj = URIRef(triple.object) if triple.object.startswith("http") else Literal(triple.object)
            
            self.graph.remove((subject, predicate, obj))
            
            return {"success": True}
        except Exception as e:
            self.logger.error(f"Failed to delete triple: {e}")
            raise ProcessingError(f"Failed to delete triple: {e}")
    
    def run_inference(
        self,
        model: Optional[Any] = None,
        **options
    ) -> Dict[str, Any]:
        """
        Execute inference rules.
        
        Args:
            model: Optional model (uses default if not provided)
            **options: Inference options
        
        Returns:
            Inference results
        """
        if not self.enable_inference:
            self.logger.warning("Inference not enabled")
            return {"success": False, "message": "Inference not enabled"}
        
        # Basic inference would require OWL reasoner
        # This is a placeholder implementation
        self.logger.info("Inference would be executed here with OWL reasoner")
        
        return {
            "success": True,
            "inferred_triples": 0,
            "message": "Inference placeholder"
        }
    
    def execute_sparql(
        self,
        query: str,
        **options
    ) -> Dict[str, Any]:
        """
        Execute SPARQL query.
        
        Args:
            query: SPARQL query string
            **options: Additional options
        
        Returns:
            Query results
        """
        if not self.graph:
            raise ProcessingError("Graph not initialized")
        
        try:
            results = self.graph.query(query)
            
            bindings = []
            variables = []
            
            if results.vars:
                variables = [str(v) for v in results.vars]
                
                for row in results:
                    binding = {}
                    for var in results.vars:
                        value = getattr(row, str(var))
                        if value:
                            binding[str(var)] = {"value": str(value), "type": "uri" if isinstance(value, URIRef) else "literal"}
                    bindings.append(binding)
            
            return {
                "success": True,
                "bindings": bindings,
                "variables": variables,
                "metadata": {"query": query}
            }
        except Exception as e:
            self.logger.error(f"SPARQL query failed: {e}")
            raise ProcessingError(f"SPARQL query failed: {e}")
    
    def serialize(self, format: str = "turtle", **options) -> str:
        """
        Serialize graph to RDF format.
        
        Args:
            format: RDF format (turtle, rdfxml, n3)
            **options: Serialization options
        
        Returns:
            Serialized RDF string
        """
        if not self.graph:
            return ""
        
        try:
            return self.graph.serialize(format=format)
        except Exception as e:
            self.logger.error(f"Serialization failed: {e}")
            return ""
