"""
Content type detection and validation service.

This module provides a unified interface for content type detection and validation
using a modular architecture with separate concerns for detection strategies,
validation, and content analysis utilities.
"""

import logging
import xml.etree.ElementTree as ET

from .detectors.binary_detector import BinarySignatureDetector
from .strategies.detection_strategy import BinaryFirstStrategy, DetectionStrategy
from .utils.content_analyzer import ContentAnalyzer

# Import modular components
from .validators.validation_registry import ValidationError, validation_registry

# Module logger
LOGGER = logging.getLogger(__name__)

# Re-export ValidationError for backward compatibility
__all__ = ["ContentTypeInterpreter", "ValidationError", "get_interpreter"]

# Module-level singleton instance (lazy)
_default_interpreter: "ContentTypeInterpreter | None" = None


def get_interpreter() -> "ContentTypeInterpreter":
    """Return the module-level ContentTypeInterpreter singleton."""
    global _default_interpreter
    if _default_interpreter is None:
        _default_interpreter = ContentTypeInterpreter()
    return _default_interpreter


class ContentTypeInterpreter:
    """
    Unified service for content type detection and validation.

    This consolidated class uses modular components internally while maintaining
    the same external API for backward compatibility.
    """

    # ─── Load shared MIME data from external JSON ─────────────────────────
    # The canonical data lives in mime_extensions.json at the repo root.
    # Both Python and TypeScript load from it, keeping them in sync.
    @staticmethod
    def _load_shared_mime_data() -> tuple[frozenset, dict]:
        """Load MIME data from the shared JSON file.

        Returns a (text_mime_types frozenset, mime_to_extension dict) tuple.
        Falls back to minimal built-in defaults if the file is not found.
        """
        import json
        from pathlib import Path

        # Walk up from this file to find mime_extensions.json at repo root
        search = Path(__file__).resolve().parent
        for _ in range(5):
            candidate = search / "mime_extensions.json"
            if candidate.exists():
                break
            search = search.parent
        else:
            candidate = None

        if candidate and candidate.exists():
            try:
                with open(candidate, encoding="utf-8") as f:
                    data = json.load(f)
                text_types = frozenset(data.get("textMimeTypes", []))
                ext_map = dict(data.get("mimeToExtension", {}))
                return text_types, ext_map
            except Exception as e:
                LOGGER.warning(
                    "Failed to load mime_extensions.json: %s — using defaults", e
                )

        # Minimal fallback if JSON file is missing
        return frozenset({"text/plain", "application/json"}), {
            "text/plain": "txt",
            "application/json": "json",
        }

    # Populated from the shared JSON (or fallback)
    _text_types, _ext_map = _load_shared_mime_data.__func__()  # type: ignore[attr-defined]
    TEXT_MIME_TYPES: frozenset = _text_types
    _MIME_TO_EXTENSION: dict = _ext_map

    @classmethod
    def register_extension(cls, mime_type: str, extension: str) -> None:
        """Dynamically register a new MIME-to-extension mapping.

        Accepts extensions with or without a leading dot (e.g., '.custom' or 'custom').
        The stored value will always be without a leading dot, consistent with the existing mapping.
        """
        if extension.startswith("."):
            extension = extension[1:]
        cls._MIME_TO_EXTENSION[mime_type] = extension

    @classmethod
    def register_extensions(cls, mapping: dict) -> None:
        """Dynamically register a batch of MIME-to-extension mappings."""
        for mime, ext in mapping.items():
            cls.register_extension(mime, ext)

    def __init__(self, detection_strategy: DetectionStrategy | None = None):
        """
        Initialize the interpreter with a detection strategy.

        Args:
            detection_strategy: Strategy for content type detection.
                               Defaults to BinaryFirstStrategy for compatibility.
        """
        self._detection_strategy = detection_strategy or BinaryFirstStrategy()

    @staticmethod
    def _decode_utf8(data: bytes) -> str:
        """Decode bytes to UTF-8 string using a consistent policy."""
        return data.decode("utf-8", errors="replace")

    @staticmethod
    def detect_content_type(
        content: str | bytes, file_extension: str = None
    ) -> tuple[str, str]:
        """
        Detect content type and suggested extension.

        This method now uses the modular detection strategy while maintaining
        the same external interface for backward compatibility.

        Args:
            content: The content to analyze
            file_extension: Optional file extension hint

        Returns:
            Tuple of (mime_type, extension)
        """
        # Use default strategy for static method (maintains backward compatibility)
        strategy = BinaryFirstStrategy()
        return strategy.detect(content, file_extension)

    def detect_content_type_with_strategy(
        self, content: str | bytes, file_extension: str = None
    ) -> tuple[str, str]:
        """
        Detect content type using the configured strategy.

        Args:
            content: The content to analyze
            file_extension: Optional file extension hint

        Returns:
            Tuple of (mime_type, extension)
        """
        return self._detection_strategy.detect(content, file_extension)

    # Backward compatibility methods that delegate to ContentAnalyzer
    @staticmethod
    def _is_known_long_line_extension(file_extension: str | None) -> bool:
        """Backward compatibility wrapper for ContentAnalyzer."""
        return ContentAnalyzer.is_known_long_line_extension(file_extension)

    @staticmethod
    def _is_unstructured_binary(sample: bytes) -> bool:
        """Backward compatibility wrapper for ContentAnalyzer."""
        return ContentAnalyzer.is_unstructured_binary(sample)

    @staticmethod
    def _has_pathological_lines(sample: bytes, is_known_type: bool) -> bool:
        """Backward compatibility wrapper for ContentAnalyzer."""
        return ContentAnalyzer.has_pathological_lines(sample, is_known_type)

    @staticmethod
    def _is_problematic_bytes(
        content: bytes, file_extension: str | None = None
    ) -> bool:
        """Backward compatibility wrapper for ContentAnalyzer."""
        return ContentAnalyzer.is_problematic_bytes(content, file_extension)

    @staticmethod
    def _detect_by_signature(content: bytes) -> str:
        """Backward compatibility method that delegates to BinarySignatureDetector."""
        detector = BinarySignatureDetector()
        return detector.detect_from_bytes(content)

    @staticmethod
    def _detect_bytes_content(content: bytes) -> str:
        """Backward compatibility method that uses the detection strategy."""
        strategy = BinaryFirstStrategy()
        mime_type, _ = strategy.detect(content)
        return mime_type

    def validate_content(self, content: str | bytes) -> None:
        """
        Validate content based on its detected type.

        This method now delegates to the validation registry for better modularity.

        Args:
            content: The content to validate

        Raises:
            ValidationError: If content is invalid
        """
        mime_type, _ = self._detection_strategy.detect(content)
        validation_registry.validate(content, mime_type)

    @staticmethod
    def validate_content_static(content: str | bytes) -> None:
        """
        Static method for validating content (backward compatibility).

        Args:
            content: The content to validate

        Raises:
            ValidationError: If content is invalid
        """
        # Use default strategy for static method
        strategy = BinaryFirstStrategy()
        mime_type, _ = strategy.detect(content)
        validation_registry.validate(content, mime_type)

    @staticmethod
    def is_binary_content(content: str | bytes) -> bool:
        """
        Determine if content should be treated as binary.

        This method uses multiple heuristics:
        1. If content is already a string, it's not binary
        2. For bytes content:
           - Check for known binary signatures
           - Try UTF-8 decoding
           - Analyze content patterns
        """
        # Delegate to BinarySignatureDetector's is_binary_content method
        # If content is already a string, it's not binary
        if isinstance(content, str):
            return False

        # Check for binary signatures
        for signature, _ in BinarySignatureDetector.SIGNATURES.items():
            if content.startswith(signature):
                return True

        # Try to decode as UTF-8
        try:
            # If content has null bytes, it's likely binary
            if b"\x00" in content:
                return True

            # Attempt to decode as UTF-8
            content.decode("utf-8", errors="strict")

            # If we get here, the content can be decoded as UTF-8
            # Check for high concentration of non-printable chars (indicating binary)
            sample = content[:4096] if len(content) > 4096 else content

            # Handle empty content - not binary (it's just empty text)
            if len(sample) == 0:
                return False

            non_text_chars = sum(
                1 for b in sample if b < 9 or (b > 13 and b < 32) or b > 126
            )
            text_ratio = 1 - (non_text_chars / len(sample))

            # If more than 30% non-printable characters, consider it binary
            return text_ratio < 0.7

        except UnicodeDecodeError:
            # If it can't be decoded as UTF-8, it's binary
            return True

    @staticmethod
    def is_xml_content(content: str | bytes) -> bool:
        """Check if content is valid XML."""
        # Delegate to XMLDetector's is_valid_xml method
        try:
            if isinstance(content, str):
                content = content.encode("utf-8")

            # Try to parse the XML without requiring XML declaration
            ET.fromstring(content)
            LOGGER.debug("Valid XML content detected.")
            return True
        except Exception as e:
            LOGGER.debug("Invalid XML content detected: %s", str(e))
            return False

    @staticmethod
    def is_svg_content(content: str | bytes) -> bool:
        """Check if content is SVG."""
        if isinstance(content, bytes):
            content = content.decode("utf-8", errors="ignore")

        # First check if it's valid XML
        if not ContentTypeInterpreter.is_xml_content(content):
            return False

        try:
            # Parse XML and check for SVG namespace
            tree = ET.fromstring(content)
            return (
                tree.tag == "svg"
                or tree.tag.endswith("}svg")
                or any(
                    attr.endswith("xmlns") and "svg" in value
                    for attr, value in tree.attrib.items()
                )
            )
        except Exception:
            return False

    @staticmethod
    def is_mermaid_content(content: str) -> bool:
        """Check if content is Mermaid diagram."""
        content = content.strip().lower()
        mermaid_keywords = [
            "graph ",
            "sequencediagram",
            "classDiagram",
            "stateDiagram",
            "erDiagram",
            "gantt",
            "pie",
            "flowchart",
            "journey",
        ]
        return any(content.startswith(keyword.lower()) for keyword in mermaid_keywords)

    @staticmethod
    def is_diagram_content(content: str) -> bool:
        """Check if content is a diagram format."""
        content = content.strip().lower()
        # Check for PlantUML
        if content.startswith("@startuml") and content.endswith("@enduml"):
            return True
        # Check for Graphviz
        if content.startswith(("digraph", "graph", "strict")):
            return True
        # Check for Mermaid
        return ContentTypeInterpreter.is_mermaid_content(content)

    @staticmethod
    def get_extension(mime_type: str) -> str:
        """Get file extension from MIME type."""
        return ContentTypeInterpreter._MIME_TO_EXTENSION.get(mime_type, "")

    @staticmethod
    def get_default_extension(mime_type: str) -> str:
        """
        Return the default file extension for a given MIME type.
        """
        return ContentTypeInterpreter._MIME_TO_EXTENSION.get(mime_type, "")
