import os
import sys
import json
import hashlib
import subprocess
import importlib
import warnings
from pathlib import Path
from typing import Optional

from .exceptions import (
    CppApiError,
    CppBuildError,
    CppModuleNotFoundError,
    CppModuleOutdatedError,
    CppReloadWarning,
    CppValidationError
)
from .build_manager import BuildManager

class CppApi:
    def __init__(self, verbose: bool = False, auto_update: bool = False):
        """Initialize C++ API.

        Args:
            verbose: Enable detailed logging
            auto_update: Auto-rebuild outdated modules on include()
        """
        self.project_root = Path(__file__).parent.parent.parent.parent
        self.registry_file = self.project_root / ".module_registry.json"
        self.python_dir = self.project_root / "src" / "python"
        self.rebuild_script = self.project_root / "rebuild.bat"
        self.verbose = verbose
        self.auto_update = auto_update
        self.silent = False

        self.build_manager = BuildManager(self.project_root)

        if str(self.python_dir) not in sys.path:
            sys.path.insert(0, str(self.python_dir))

        self.registry = self._load_registry()
        self.api_module = None
        self._import_api()

    def _load_registry(self):
        if not self.registry_file.exists():
            return {}

        try:
            with open(self.registry_file, 'r') as f:
                return json.load(f)
        except Exception:
            return {}

    def _save_registry(self):
        try:
            with open(self.registry_file, 'w') as f:
                json.dump(self.registry, f, indent=2)
        except Exception:
            pass

    def _import_api(self):
        try:
            if 'api' in sys.modules:
                self.api_module = sys.modules['api']
            else:
                import api
                self.api_module = api
        except ImportError:
            self.api_module = None

    def _compute_hash(self, filepath):
        if not os.path.exists(filepath):
            return "0"

        try:
            with open(filepath, 'rb') as f:
                return hashlib.sha256(f.read()).hexdigest()
        except Exception:
            return "0"

    def exists(self, module_name):
        if module_name not in self.registry:
            return False

        if self.api_module is None:
            return False

        try:
            return hasattr(self.api_module, module_name)
        except Exception:
            return False

    def CheckForUpdate(self, module_name):
        if module_name not in self.registry:
            return False

        module_info = self.registry[module_name]

        for filename, stored_hash in module_info.get('hashes', {}).items():
            if filename.endswith('.cpp'):
                filepath = self.project_root / module_info['sources'][0]
            elif filename.endswith('.h'):
                header = module_info.get('header')
                if header is None:
                    continue
                filepath = self.project_root / header
            elif filename.endswith('.cp'):
                filepath = self.project_root / module_info['cp_file']
            else:
                continue

            current_hash = self._compute_hash(str(filepath))
            if current_hash != stored_hash:
                return True

        return False

    def _print_progress(self, step: int, total: int, description: str):
        """Progress callback for builds."""
        if self.verbose:
            print(f"[{step + 1}/{total}] {description}...")

    def reload(self, module_name: Optional[str] = None, force: bool = False) -> bool:
        """Intelligently reload modules.

        Args:
            module_name: Specific module to rebuild, or None for changed modules only
            force: Rebuild even if no changes detected

        Returns:
            True if rebuild succeeded

        Raises:
            CppBuildError: If build fails
            CppModuleNotFoundError: If module_name not in registry

        WARNING: C extensions cannot be safely reloaded. After rebuild, RESTART PYTHON.
        """
        from typing import Optional

        # Specific module rebuild
        if module_name:
            if module_name not in self.registry:
                raise CppModuleNotFoundError(
                    f"Module '{module_name}' not found. "
                    f"Available: {list(self.registry.keys())}"
                )

            if not force and not self.CheckForUpdate(module_name):
                if self.verbose:
                    print(f"Module '{module_name}' is up to date")
                return False

            if self.verbose:
                print(f"Rebuilding module: {module_name}")

        else:
            # Detect changed modules
            changed = self.build_manager.detect_changed_modules(self.registry)

            if not changed and not force:
                if self.verbose:
                    print("All modules are up to date")
                return False

            if self.verbose:
                print(f"Changed modules detected: {', '.join(changed)}")

        # Check if generator itself changed
        if self.build_manager.check_generator_changed():
            if self.verbose:
                print("Plugin generator changed, rebuilding it first...")
            self.build_manager.rebuild_generator(verbose=self.verbose)

        # Rebuild modules
        try:
            self.build_manager.rebuild_modules(
                progress_callback=self._print_progress if self.verbose else None
            )

            # Reload registry
            self.registry = self._load_registry()

            # Warn about C extension reload
            if 'api' in sys.modules:
                if not self.silent:
                    warnings.warn(
                        "C++ module rebuilt successfully. "
                        "RESTART PYTHON to load the new version. "
                        "Continuing with old version in this session.",
                        CppReloadWarning,
                        stacklevel=2
                    )
                return True

            # If not yet loaded, import it
            self._import_api()
            return True

        except Exception as e:
            raise CppBuildError(f"Reload failed: {e}") from e

    def rebuild(self):
        """Rebuild all modules (legacy method, use reload() instead).

        WARNING: C extensions cannot be safely reloaded. RESTART PYTHON after rebuild.

        Returns:
            bool: True if rebuild succeeded

        Raises:
            CppBuildError: If build fails
        """
        return self.reload(force=True)

    def include(self, module_name: str, auto_update: Optional[bool] = None):
        """Load a C++ module with optional auto-update.

        Args:
            module_name: Name of module to load
            auto_update: Override instance auto_update setting (None = use instance setting)

        Returns:
            Module object

        Raises:
            CppModuleNotFoundError: If module not found in registry
        """
        from typing import Optional

        if module_name not in self.registry:
            raise CppModuleNotFoundError(
                f"Module '{module_name}' not registered. "
                f"Check src/plugins/{module_name}.cp exists and rebuild."
            )

        # Determine auto_update behavior
        do_update = auto_update if auto_update is not None else self.auto_update

        # Check for updates
        if self.CheckForUpdate(module_name):
            if do_update:
                if self.verbose:
                    print(f"Auto-updating module '{module_name}'...")
                try:
                    self.reload(module_name)
                except CppBuildError as e:
                    if not self.silent:
                        warnings.warn(
                            f"Auto-update failed: {e}. Using existing version.",
                            CppReloadWarning,
                            stacklevel=2
                        )
            else:
                if not self.silent:
                    warnings.warn(
                        f"Module '{module_name}' is outdated. "
                        f"Call CPP.reload('{module_name}') to update.",
                        UserWarning,
                        stacklevel=2
                    )

        # Return module
        if not self.exists(module_name):
            raise CppModuleNotFoundError(
                f"Module '{module_name}' in registry but not in api.pyd. "
                f"Rebuild required."
            )

        try:
            return getattr(self.api_module, module_name)
        except AttributeError:
            raise CppModuleNotFoundError(
                f"Module '{module_name}' not found in api.pyd. "
                f"This is a bug - please rebuild."
            )

    def noFeedback(self):
        """Disable all warnings and feedback messages."""
        self.silent = True

    def list_modules(self):
        return list(self.registry.keys())

    def get_module_info(self, module_name):
        if module_name not in self.registry:
            return None

        return self.registry[module_name]
