import asyncio
from pathlib import Path
from collections import defaultdict
from chitai.database.models.library import Library
from chitai.services import BookService, LibraryService
from chitai.services.metadata_extractor import Extractor
from chitai.services.utils import create_directory
from watchfiles import awatch, Change

class ConsumeDirectoryWatcher:
    """Watches a directory and batch processes files by their relative path."""
    
    def __init__(self, watch_path: str, library_service: LibraryService, book_service: BookService,  batch_delay: float = 3.0):
        """
        Initialize the file watcher.
        
        Args:
            watch_path: Directory path to watch
            batch_delay: Seconds to wait before processing a batch
        """
        self.watch_path = Path(watch_path).resolve()  # Convert to absolute path
        self.batch_delay = batch_delay
        self.file_groups: dict[str, set[Path]] = defaultdict(set)
        self._processing_tasks = set()

        self.book_service = book_service
        self.library_service = library_service

    async def init_watcher(self):
        """Background task that watches for file changes."""

        # Create consume directories for each library if they do not exist
        libraries = await self.library_service.list()
        for lib in libraries:
            await create_directory(Path(self.watch_path) / Path(lib.slug))

        print(f"Starting file watcher on {self.watch_path}")
        try:
            async for changes in awatch(str(self.watch_path)):
                for change_type, file_path in changes:
                    if change_type != Change.added:
                        continue
                    
                    file_path = Path(file_path)
                    
                    # If a directory was added, scan it for existing files
                    if file_path.is_dir():
                        print(f"Directory added: {file_path}")
                        await self._handle_directory_added(file_path)
                    else:
                        print(f"File added: {file_path}")
                        await self._handle_file_added(file_path)
                    
        except asyncio.CancelledError:
            print("File watcher stopped")
            # Wait for any pending processing tasks
            if self._processing_tasks:
                await asyncio.gather(*self._processing_tasks, return_exceptions=True)
            raise
    
    async def _handle_file_added(self, file_path: Path):
        """Handle a single file being added."""
        # Get relative path from watch directory
        
        rel_path = file_path.relative_to(self.watch_path)
        parent_rel = rel_path.parent
        library = parent_rel.parts[0]

        # Add to appropriate group
        self.file_groups[library].add(file_path)
        
        # Schedule batch processing for this group
        self._schedule_batch_processing(library)
    
    async def _handle_directory_added(self, dir_path: Path):
        """Handle a directory being added - scan it for existing files."""
        try:
            # Recursively find all files in the added directory
            for file_path in dir_path.rglob("*"):
                if file_path.is_file():
                    print(f"Found existing file: {file_path}")
                    await self._handle_file_added(file_path)
        except Exception as e:
            print(f"Error scanning directory {dir_path}: {e}")
    
    def _schedule_batch_processing(self, library_slug: str):
        """Schedule batch processing for a specific path group."""
        # Create a task to process this group after a delay
        task = asyncio.create_task(self._delayed_batch_process(library_slug))
        self._processing_tasks.add(task)
        task.add_done_callback(self._processing_tasks.discard)
    
    async def _delayed_batch_process(self, library_slug: str):
        """Wait for batch delay, then process accumulated files."""
        await asyncio.sleep(self.batch_delay)
        
        # Get and clear the file list for this path
        if library_slug not in self.file_groups:
            return
            
        files_to_process = self.file_groups[library_slug].copy()
        self.file_groups[library_slug].clear()
        
        if not files_to_process:
            return
        
        print(f"Batch processing {len(files_to_process)} files from {library_slug}")
        await self._process_batch(files_to_process, library_slug)
    
    async def _process_batch(self, file_paths: set[Path], library_slug: str):
        """Process a batch of files."""
        try:

            books = await self.book_service.create_many_from_existing_files(
                list(file_paths),
                self.watch_path / Path(library_slug),
                library=await self._get_library(library_slug),
            )

            print(f"Created {len(books)} books!")
            
        except Exception as e:
            print(f"Error processing batch: {e}")
            raise e
        
    async def _get_library(self, slug: str) -> Library:
        return await self.library_service.get_one(Library.slug == slug)
