"""
Store Profiles Registry with Hot-Reload Support.

This module provides a registry for store-specific OCR extraction profiles.
Profiles can be reloaded at runtime without restarting the server.

Usage:
    from backend.modules.data_entry.services.ocr.profiles import ProfileRegistry

    # Get profile for a CUI
    profile = ProfileRegistry.get_profile("22891860")
    if profile:
        tva_entries = profile.extract_tva_entries(text)

    # Reload all profiles (after file changes)
    count = ProfileRegistry.reload_all()

Architecture:
    - ProfileRegistry: Singleton registry with class methods
    - BaseStoreProfile: Abstract base class for profiles
    - @ProfileRegistry.register: Decorator for profile classes

Hot-Reload Mechanism:
    1. Admin calls POST /profiles/reload endpoint
    2. Registry clears instance cache
    3. importlib.reload() re-executes each profile module
    4. @register decorator re-registers classes with new code
"""

from __future__ import annotations

import importlib
import logging
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Type, TYPE_CHECKING

if TYPE_CHECKING:
    from .base import BaseStoreProfile

logger = logging.getLogger(__name__)

# Directory containing profile modules
PROFILES_DIR = Path(__file__).parent


class ProfileRegistry:
    """
    Registry for store-specific OCR extraction profiles.

    Uses class methods for singleton-like behavior without explicit instantiation.
    Supports hot-reload via importlib.reload() for runtime updates.

    Attributes:
        _profiles: Maps CUI -> profile class (not instance)
        _instances: Maps CUI -> profile instance (lazy, cleared on reload)
        _last_reload: Timestamp of last reload
        _loaded: Whether initial load has been performed
    """

    # Class-level storage (singleton pattern via class methods)
    _profiles: Dict[str, Type["BaseStoreProfile"]] = {}
    _instances: Dict[str, "BaseStoreProfile"] = {}
    _last_reload: Optional[datetime] = None
    _loaded: bool = False

    # -------------------------------------------------------------------------
    # Registration
    # -------------------------------------------------------------------------

    @classmethod
    def register(cls, profile_class: Type["BaseStoreProfile"]) -> Type["BaseStoreProfile"]:
        """
        Decorator to register a store profile class.

        Registers the profile for all CUIs in the class's CUI_LIST.
        Safe for re-registration during hot-reload (overwrites existing).

        Usage:
            @ProfileRegistry.register
            class LidlProfile(BaseStoreProfile):
                CUI_LIST = ["22891860"]
                ...

        Args:
            profile_class: Profile class to register

        Returns:
            The same class (allows use as decorator)

        Raises:
            ValueError: If CUI_LIST is empty
        """
        cui_list = getattr(profile_class, 'CUI_LIST', [])
        store_name = getattr(profile_class, 'STORE_NAME', profile_class.__name__)

        if not cui_list:
            logger.warning(f"Profile {profile_class.__name__} has empty CUI_LIST, skipping")
            return profile_class

        # Register for each CUI
        for cui in cui_list:
            # Normalize CUI (remove RO prefix, strip whitespace)
            normalized_cui = cls._normalize_cui(cui)

            if normalized_cui in cls._profiles:
                old_class = cls._profiles[normalized_cui]
                logger.debug(
                    f"Re-registering CUI {normalized_cui}: "
                    f"{old_class.__name__} -> {profile_class.__name__}"
                )
                # Clear cached instance for this CUI
                cls._instances.pop(normalized_cui, None)

            cls._profiles[normalized_cui] = profile_class
            logger.debug(f"Registered profile {profile_class.__name__} for CUI {normalized_cui}")

        logger.info(f"Registered {store_name} for CUIs: {cui_list}")
        return profile_class

    # -------------------------------------------------------------------------
    # Lookup
    # -------------------------------------------------------------------------

    @classmethod
    def get_profile(cls, cui: Optional[str]) -> Optional["BaseStoreProfile"]:
        """
        Get profile instance for a CUI.

        Uses lazy instantiation - creates instance on first access.
        Returns None if no profile is registered for this CUI.

        Args:
            cui: CUI to lookup (with or without RO prefix)

        Returns:
            Profile instance or None
        """
        if not cui:
            return None

        # Ensure profiles are loaded
        if not cls._loaded:
            cls._load_all_profiles()

        normalized_cui = cls._normalize_cui(cui)

        # Check if profile exists
        profile_class = cls._profiles.get(normalized_cui)
        if not profile_class:
            return None

        # Lazy instantiation
        if normalized_cui not in cls._instances:
            try:
                cls._instances[normalized_cui] = profile_class()
                logger.debug(f"Instantiated {profile_class.__name__} for CUI {normalized_cui}")
            except Exception as e:
                logger.error(f"Failed to instantiate {profile_class.__name__}: {e}")
                return None

        return cls._instances[normalized_cui]

    @classmethod
    def has_profile(cls, cui: Optional[str]) -> bool:
        """Check if a profile exists for this CUI."""
        if not cui:
            return False
        if not cls._loaded:
            cls._load_all_profiles()
        return cls._normalize_cui(cui) in cls._profiles

    # -------------------------------------------------------------------------
    # Listing
    # -------------------------------------------------------------------------

    @classmethod
    def list_profiles(cls) -> List[Dict]:
        """
        List all registered profiles.

        Returns:
            List of dicts with cui, class_name, store_name, name_patterns
        """
        if not cls._loaded:
            cls._load_all_profiles()

        result = []
        seen_classes = set()

        for cui, profile_class in cls._profiles.items():
            # Avoid duplicates for profiles with multiple CUIs
            if profile_class.__name__ in seen_classes:
                continue
            seen_classes.add(profile_class.__name__)

            result.append({
                "cuis": list(getattr(profile_class, 'CUI_LIST', [])),
                "class_name": profile_class.__name__,
                "store_name": getattr(profile_class, 'STORE_NAME', profile_class.__name__),
                "name_patterns": list(getattr(profile_class, 'NAME_PATTERNS', [])),
            })

        return result

    @classmethod
    def get_profile_info(cls, cui: str) -> Optional[Dict]:
        """
        Get detailed info about a profile.

        Args:
            cui: CUI to lookup

        Returns:
            Dict with profile details or None
        """
        profile = cls.get_profile(cui)
        if not profile:
            return None

        return {
            "cui": cui,
            "cuis": list(profile.CUI_LIST),
            "class_name": profile.__class__.__name__,
            "store_name": profile.STORE_NAME,
            "name_patterns": list(profile.NAME_PATTERNS),
            "validation_hints": profile.get_validation_hints(),
        }

    # -------------------------------------------------------------------------
    # Hot-Reload
    # -------------------------------------------------------------------------

    @classmethod
    def reload_all(cls) -> int:
        """
        Hot-reload all profile modules.

        Clears instance cache and reloads all .py files in profiles directory.
        Decorator re-registers classes with updated code.

        Returns:
            Number of modules reloaded
        """
        logger.info("Starting profile hot-reload...")

        # Clear instance cache (will be recreated on next get_profile)
        cls._instances.clear()

        # Get list of profile modules (exclude __init__, base)
        module_names = cls._get_profile_module_names()

        # Determine the module prefix based on how THIS module was imported
        base_package = cls.__module__

        count = 0
        for module_name in module_names:
            full_name = f"{base_package}.{module_name}"

            try:
                if full_name in sys.modules:
                    # Reload existing module
                    importlib.reload(sys.modules[full_name])
                    logger.debug(f"Reloaded module: {module_name}")
                else:
                    # Import new module
                    importlib.import_module(full_name)
                    logger.debug(f"Imported new module: {module_name}")
                count += 1
            except Exception as e:
                logger.error(f"Failed to reload {module_name}: {e}")

        cls._last_reload = datetime.utcnow()
        cls._loaded = True

        logger.info(f"Profile hot-reload complete: {count} modules, {len(cls._profiles)} profiles")
        return count

    @classmethod
    def get_reload_status(cls) -> Dict:
        """Get status of the registry including last reload time."""
        return {
            "loaded": cls._loaded,
            "last_reload": cls._last_reload.isoformat() if cls._last_reload else None,
            "profiles_count": len(cls._profiles),
            "instances_count": len(cls._instances),
            "registered_cuis": list(cls._profiles.keys()),
        }

    # -------------------------------------------------------------------------
    # Internal methods
    # -------------------------------------------------------------------------

    @classmethod
    def _normalize_cui(cls, cui: str) -> str:
        """
        Normalize CUI for consistent lookup.

        - Removes RO prefix (with or without space)
        - Strips whitespace
        - Converts to uppercase

        Args:
            cui: Raw CUI string

        Returns:
            Normalized CUI (digits only)
        """
        if not cui:
            return ""

        cui = str(cui).strip().upper()

        # Remove RO prefix (handles "RO12345" and "RO 12345")
        if cui.startswith("RO"):
            cui = cui[2:].lstrip()

        return cui.strip()

    @classmethod
    def _get_profile_module_names(cls) -> List[str]:
        """
        Get list of profile module names from profiles directory.

        Excludes __init__.py and base.py.

        Returns:
            List of module names (without .py extension)
        """
        excluded = {"__init__", "base", "__pycache__"}
        modules = []

        for path in PROFILES_DIR.glob("*.py"):
            name = path.stem
            if name not in excluded:
                modules.append(name)

        return sorted(modules)

    @classmethod
    def _load_all_profiles(cls) -> None:
        """
        Initial load of all profile modules.

        Called automatically on first get_profile() if not already loaded.
        """
        if cls._loaded:
            return

        logger.info("Loading store profiles...")

        module_names = cls._get_profile_module_names()

        # Determine the module prefix based on how THIS module was imported
        # This handles both:
        # - Running from backend dir: "modules.data_entry.services.ocr.profiles"
        # - Running from project root: "backend.modules.data_entry.services.ocr.profiles"
        this_module = cls.__module__  # e.g. "backend.modules..." or "modules..."
        base_package = this_module  # Use the same prefix for child modules

        for module_name in module_names:
            full_name = f"{base_package}.{module_name}"
            try:
                importlib.import_module(full_name)
                logger.debug(f"Loaded module: {module_name}")
            except Exception as e:
                logger.error(f"Failed to load {module_name}: {e}")

        cls._loaded = True
        cls._last_reload = datetime.utcnow()

        logger.info(f"Loaded {len(cls._profiles)} store profiles")

    @classmethod
    def clear(cls) -> None:
        """
        Clear all registered profiles.

        Mainly useful for testing.
        """
        cls._profiles.clear()
        cls._instances.clear()
        cls._loaded = False
        cls._last_reload = None


# -------------------------------------------------------------------------
# Module exports
# -------------------------------------------------------------------------

__all__ = [
    "ProfileRegistry",
    "BaseStoreProfile",
]

# Re-export BaseStoreProfile for convenience
from .base import BaseStoreProfile
