import logging
from pathlib import Path
from typing import Optional

from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

from .core.path_resolver import PathResolver
from .dispatcher.dispatcher_enhanced import EnhancedDispatcher
from .plugins.language_registry import get_all_extensions

# Note: We've removed ignore pattern checks - ALL files are indexed locally
# from .core.ignore_patterns import should_ignore_file

logger = logging.getLogger(__name__)


class _Handler(FileSystemEventHandler):
    def __init__(
        self,
        dispatcher: EnhancedDispatcher,
        query_cache=None,
        path_resolver: Optional[PathResolver] = None,
    ):
        self.dispatcher = dispatcher
        self.query_cache = query_cache
        self.path_resolver = path_resolver or PathResolver()
        # Track supported file extensions - dynamically get all supported extensions
        self.code_extensions = get_all_extensions()

    async def invalidate_cache_for_file(self, path: Path):
        """Invalidate cache entries that depend on the changed file."""
        if self.query_cache:
            try:
                count = await self.query_cache.invalidate_file_queries(str(path))
                if count > 0:
                    logger.debug(f"Invalidated {count} cache entries for file {path}")
            except Exception as e:
                logger.error(f"Error invalidating cache for {path}: {e}")

    def trigger_reindex(self, path: Path):
        """Trigger re-indexing of a single file through the dispatcher."""
        try:
            if path.suffix in self.code_extensions:
                logger.info(f"Triggering re-index for {path}")
                self.dispatcher.index_file(path)

                # Invalidate cache entries for this file
                if self.query_cache:
                    import asyncio

                    try:
                        # Run cache invalidation in background
                        loop = asyncio.get_event_loop()
                        if loop.is_running():
                            asyncio.create_task(self.invalidate_cache_for_file(path))
                        else:
                            asyncio.run(self.invalidate_cache_for_file(path))
                    except Exception as e:
                        logger.warning(f"Failed to invalidate cache for {path}: {e}")
        except Exception as e:
            logger.error(f"Error re-indexing {path}: {e}")

    def remove_file_from_index(self, path: Path):
        """Remove a file from all indexes."""
        try:
            if path.suffix in self.code_extensions:
                logger.info(f"Removing file from index: {path}")
                self.dispatcher.remove_file(path)

                # Invalidate cache entries for this file
                if self.query_cache:
                    import asyncio

                    try:
                        loop = asyncio.get_event_loop()
                        if loop.is_running():
                            asyncio.create_task(self.invalidate_cache_for_file(path))
                        else:
                            asyncio.run(self.invalidate_cache_for_file(path))
                    except Exception as e:
                        logger.warning(f"Failed to invalidate cache for {path}: {e}")
        except Exception as e:
            logger.error(f"Error removing {path} from index: {e}")

    def handle_file_move(self, old_path: Path, new_path: Path):
        """Handle file move operations efficiently."""
        try:
            if (
                old_path.suffix in self.code_extensions
                and new_path.suffix in self.code_extensions
            ):
                # Compute content hash to check if file actually changed
                if new_path.exists():
                    content_hash = self.path_resolver.compute_content_hash(new_path)
                    logger.info(f"Moving file in index: {old_path} -> {new_path}")
                    self.dispatcher.move_file(old_path, new_path, content_hash)
                else:
                    # New path doesn't exist, just remove old
                    self.remove_file_from_index(old_path)
        except Exception as e:
            logger.error(f"Error handling file move {old_path} -> {new_path}: {e}")

    def on_any_event(self, event):
        """Handle any file system event and trigger re-indexing for code files."""
        if event.is_directory:
            return

        path = Path(event.src_path)

        # For creation and modification events, trigger re-indexing
        if event.event_type in ("created", "modified"):
            if path.suffix in self.code_extensions and path.exists():
                self.trigger_reindex(path)

        # For move events, handle both source and destination
        elif event.event_type == "moved":
            old_path = Path(event.src_path)
            new_path = Path(event.dest_path)
            self.handle_file_move(old_path, new_path)

        # For deletion events, remove from index
        elif event.event_type == "deleted":
            if path.suffix in self.code_extensions:
                self.remove_file_from_index(path)

    def on_modified(self, event):
        """Handle file modification events."""
        # Let on_any_event handle this

    def on_deleted(self, event):
        """Handle file deletion events."""
        # Let on_any_event handle this

    def on_moved(self, event):
        """Handle file move events."""
        # Let on_any_event handle this

    def on_created(self, event):
        """Handle file creation events."""
        # Let on_any_event handle this


class FileWatcher:
    def __init__(
        self,
        root: Path,
        dispatcher: EnhancedDispatcher,
        query_cache=None,
        path_resolver: Optional[PathResolver] = None,
    ):
        self._observer = Observer()
        self._observer.schedule(
            _Handler(dispatcher, query_cache, path_resolver), str(root), recursive=True
        )

    def start(self):
        self._observer.start()

    def stop(self):
        self._observer.stop()
        self._observer.join(timeout=5)
