"""
Company: eXonware.com
Author: Eng. Muhammad AlShehri
Email: connect@exonware.com
Version: 0.0.1.399
Generation Date: November 2, 2025

JSON serialization - Universal, human-readable data interchange format.

Following I→A pattern:
- I: ISerialization (interface)
- A: ASerialization (abstract base)
- Concrete: JsonSerializer
"""

import json
from typing import Any, Optional, Union
from pathlib import Path

from ...base import ASerialization
from ....contracts import EncodeOptions, DecodeOptions
from ....defs import CodecCapability
from ....errors import SerializationError


class JsonSerializer(ASerialization):
    """
    JSON serializer - follows the I→A pattern.
    
    I: ISerialization (interface)
    A: ASerialization (abstract base)
    Concrete: JsonSerializer
    
    Uses Python's built-in `json` library for reliable JSON handling.
    
    Examples:
        >>> serializer = JsonSerializer()
        >>> 
        >>> # Encode data
        >>> json_str = serializer.encode({"key": "value"})
        >>> # b'{"key": "value"}'
        >>> 
        >>> # Decode data
        >>> data = serializer.decode(b'{"key": "value"}')
        >>> # {'key': 'value'}
        >>> 
        >>> # Save to file
        >>> serializer.save_file({"name": "John"}, "user.json")
        >>> 
        >>> # Load from file
        >>> user = serializer.load_file("user.json")
    """
    
    # ========================================================================
    # CODEC METADATA
    # ========================================================================
    
    @property
    def codec_id(self) -> str:
        return "json"
    
    @property
    def media_types(self) -> list[str]:
        return ["application/json", "text/json"]
    
    @property
    def file_extensions(self) -> list[str]:
        return [".json", ".webmanifest", ".mcmeta", ".geojson", ".topojson"]
    
    @property
    def format_name(self) -> str:
        return "JSON"
    
    @property
    def mime_type(self) -> str:
        return "application/json"
    
    @property
    def is_binary_format(self) -> bool:
        return False  # JSON is text-based
    
    @property
    def supports_streaming(self) -> bool:
        return False  # Standard JSON doesn't support streaming
    
    @property
    def capabilities(self) -> CodecCapability:
        return CodecCapability.BIDIRECTIONAL
    
    @property
    def aliases(self) -> list[str]:
        return ["json", "JSON"]
    
    # ========================================================================
    # ADVANCED FEATURE CAPABILITIES
    # ========================================================================
    
    @property
    def supports_path_based_updates(self) -> bool:
        """JSON supports path-based updates via JSONPointer."""
        return True
    
    @property
    def supports_atomic_path_write(self) -> bool:
        """JSON supports atomic path writes using JSONPointer."""
        return True
    
    @property
    def supports_query(self) -> bool:
        """JSON supports queries via JSONPath."""
        return True
    
    # ========================================================================
    # CORE ENCODE/DECODE (Using official json library)
    # ========================================================================
    
    def encode(self, value: Any, *, options: Optional[EncodeOptions] = None) -> Union[bytes, str]:
        """
        Encode data to JSON string.
        
        Uses Python's built-in json.dumps().
        
        Args:
            value: Data to serialize
            options: JSON options (indent, sort_keys, ensure_ascii, etc.)
        
        Returns:
            JSON string (as text, not bytes for compatibility)
        
        Raises:
            SerializationError: If encoding fails
        """
        try:
            opts = options or {}
            
            # Common JSON options
            indent = opts.get('indent', opts.get('pretty', None))
            if indent is True:
                indent = 2
            
            sort_keys = opts.get('sort_keys', False)
            ensure_ascii = opts.get('ensure_ascii', False)
            
            # Encode to JSON string
            json_str = json.dumps(
                value,
                indent=indent,
                sort_keys=sort_keys,
                ensure_ascii=ensure_ascii,
                default=opts.get('default', None),
                cls=opts.get('cls', None)
            )
            
            return json_str
            
        except (TypeError, ValueError, OverflowError) as e:
            raise SerializationError(
                f"Failed to encode JSON: {e}",
                format_name=self.format_name,
                original_error=e
            )
    
    def decode(self, repr: Union[bytes, str], *, options: Optional[DecodeOptions] = None) -> Any:
        """
        Decode JSON string to data.
        
        Uses Python's built-in json.loads().
        
        Args:
            repr: JSON string (bytes or str)
            options: JSON options (object_hook, parse_float, etc.)
        
        Returns:
            Decoded Python object
        
        Raises:
            SerializationError: If decoding fails
        """
        try:
            # Convert bytes to str if needed
            if isinstance(repr, bytes):
                repr = repr.decode('utf-8')
            
            opts = options or {}
            
            # Decode from JSON string
            data = json.loads(
                repr,
                object_hook=opts.get('object_hook', None),
                parse_float=opts.get('parse_float', None),
                parse_int=opts.get('parse_int', None),
                parse_constant=opts.get('parse_constant', None),
                cls=opts.get('cls', None)
            )
            
            return data
            
        except (json.JSONDecodeError, ValueError, UnicodeDecodeError) as e:
            raise SerializationError(
                f"Failed to decode JSON: {e}",
                format_name=self.format_name,
                original_error=e
            )
    
    # ========================================================================
    # ADVANCED FEATURES (Path-based operations)
    # ========================================================================
    
    def atomic_update_path(
        self, 
        file_path: Union[str, Path], 
        path: str, 
        value: Any, 
        **options
    ) -> None:
        """
        Atomically update a single path in a JSON file using JSONPointer.
        
        Uses jsonpointer library for efficient path operations. For large files,
        this still loads the entire file but provides atomic write guarantees.
        Future optimizations could use streaming JSON parsers for true partial updates.
        
        Args:
            file_path: Path to the JSON file
            path: JSONPointer path (e.g., "/users/0/name")
            value: Value to set at the specified path
            **options: Options (backup=True, etc.)
        
        Raises:
            SerializationError: If update fails
            ValueError: If path is invalid
            KeyError: If path doesn't exist
        
        Example:
            >>> serializer = JsonSerializer()
            >>> serializer.atomic_update_path("config.json", "/database/host", "localhost")
        """
        # Import jsonpointer (lazy loaded via lazy_package system)
        import jsonpointer
        
        try:
            path_obj = Path(file_path)
            if not path_obj.exists():
                raise FileNotFoundError(f"File not found: {path_obj}")
            
            # Validate path security
            from ...utils.path_ops import validate_path_security
            validate_path_security(path)
            
            # Load entire file (for now - future: streaming parser for large files)
            data = self.load_file(file_path, **options)
            
            # Use jsonpointer to set value
            jsonpointer.set_pointer(data, path, value)
            
            # Save atomically using AtomicFileWriter
            from ....common.atomic import AtomicFileWriter
            
            repr_data = self.encode(data, options=options or None)
            path_obj.parent.mkdir(parents=True, exist_ok=True)
            
            backup = options.get('backup', True)
            with AtomicFileWriter(path_obj, backup=backup) as writer:
                if isinstance(repr_data, bytes):
                    writer.write(repr_data)
                else:
                    encoding = options.get('encoding', 'utf-8')
                    writer.write(repr_data.encode(encoding))
                    
        except (FileNotFoundError, ValueError, KeyError, jsonpointer.JsonPointerException) as e:
            raise
        except Exception as e:
            raise SerializationError(
                f"Failed to atomically update path '{path}' in JSON file: {e}",
                format_name=self.format_name,
                original_error=e
            ) from e
    
    def atomic_read_path(
        self, 
        file_path: Union[str, Path], 
        path: str, 
        **options
    ) -> Any:
        """
        Read a single path from a JSON file using JSONPointer.
        
        Uses jsonpointer library for efficient path operations. For large files,
        this still loads the entire file. Future optimizations could use streaming
        JSON parsers for true partial reads.
        
        Args:
            file_path: Path to the JSON file
            path: JSONPointer path (e.g., "/users/0/name")
            **options: Options
        
        Returns:
            Value at the specified path
        
        Raises:
            SerializationError: If read fails
            KeyError: If path doesn't exist
        
        Example:
            >>> serializer = JsonSerializer()
            >>> host = serializer.atomic_read_path("config.json", "/database/host")
        """
        # Import jsonpointer (lazy loaded via lazy_package system)
        import jsonpointer
        
        try:
            path_obj = Path(file_path)
            if not path_obj.exists():
                raise FileNotFoundError(f"File not found: {path_obj}")
            
            # Validate path security
            from ...utils.path_ops import validate_path_security
            validate_path_security(path)
            
            # Load entire file (for now - future: streaming parser for large files)
            data = self.load_file(file_path, **options)
            
            # Use jsonpointer to get value
            return jsonpointer.resolve_pointer(data, path)
            
        except (FileNotFoundError, jsonpointer.JsonPointerException) as e:
            if isinstance(e, jsonpointer.JsonPointerException):
                raise KeyError(f"Path not found: {path}") from e
            raise
        except Exception as e:
            raise SerializationError(
                f"Failed to read path '{path}' from JSON file: {e}",
                format_name=self.format_name,
                original_error=e
            ) from e
    
    def query(
        self, 
        file_path: Union[str, Path], 
        query_expr: str, 
        **options
    ) -> Any:
        """
        Query JSON file using JSONPath expression.
        
        Uses jsonpath-ng library for JSONPath queries.
        
        Args:
            file_path: Path to the JSON file
            query_expr: JSONPath expression (e.g., "$.users[*].name")
            **options: Query options
        
        Returns:
            Query results (list of matching values)
        
        Raises:
            SerializationError: If query fails
            ValueError: If query expression is invalid
        
        Example:
            >>> serializer = JsonSerializer()
            >>> names = serializer.query("users.json", "$.users[*].name")
        """
        # Import jsonpath_ng (lazy loaded via lazy_package system)
        from jsonpath_ng import parse as parse_jsonpath
        
        try:
            path_obj = Path(file_path)
            if not path_obj.exists():
                raise FileNotFoundError(f"File not found: {path_obj}")
            
            # Load file
            data = self.load_file(file_path, **options)
            
            # Parse JSONPath expression
            jsonpath_expr = parse_jsonpath(query_expr)
            
            # Execute query
            matches = [match.value for match in jsonpath_expr.find(data)]
            
            return matches
            
        except (FileNotFoundError, ValueError) as e:
            raise
        except Exception as e:
            raise SerializationError(
                f"Failed to query JSON file with expression '{query_expr}': {e}",
                format_name=self.format_name,
                original_error=e
            ) from e

