"""
文件监控服务
使用 watchdog 库监控知识库目录的文件变化
支持增量索引自动触发
"""
import os
import time
from pathlib import Path
from typing import Optional, List, Set, Dict, Any
from threading import Thread, Event as ThreadEvent
from loguru import logger

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileCreatedEvent, FileModifiedEvent, FileDeletedEvent, FileMovedEvent

from src.config import get_config_manager, ConfigManager
from .event_bus import EventBus, EventType
from .events import FileWatcherEvent


class KnowledgeBaseEventHandler(FileSystemEventHandler):
    """知识库文件系统事件处理器"""
    
    def __init__(self, config: ConfigManager, event_bus: EventBus):
        """
        初始化事件处理器
        
        Args:
            config: 配置管理器
            event_bus: 事件总线
        """
        self.config = config
        self.event_bus = event_bus
        self.supported_extensions = {".txt", ".md", ".pdf", ".docx"}
        self.last_events: Dict[str, float] = {}  # 文件路径 -> 上次事件时间（用于去重）
        self.debounce_seconds = 2.0  # 防抖时间（秒）
        
    def _should_process(self, file_path: str) -> bool:
        """
        检查是否应该处理该文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            bool: 是否应该处理
        """
        # 检查文件扩展名
        ext = Path(file_path).suffix.lower()
        if ext not in self.supported_extensions:
            logger.debug(f"跳过不支持的文件类型: {file_path}")
            return False
            
        # 检查文件是否存在（对于删除事件，文件可能不存在）
        if not os.path.exists(file_path):
            return True  # 删除事件也需要处理
            
        # 检查文件大小（避免处理空文件）
        try:
            file_size = os.path.getsize(file_path)
            if file_size == 0:
                logger.debug(f"跳过空文件: {file_path}")
                return False
        except OSError:
            return False
            
        return True
        
    def _debounce_event(self, file_path: str) -> bool:
        """
        防抖处理：避免短时间内重复处理同一文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            bool: 是否应该处理该事件
        """
        current_time = time.time()
        last_time = self.last_events.get(file_path, 0)
        
        if current_time - last_time < self.debounce_seconds:
            logger.debug(f"防抖跳过: {file_path} (上次处理: {current_time - last_time:.1f}秒前)")
            return False
            
        self.last_events[file_path] = current_time
        return True
        
    def _publish_file_event(self, event_type: EventType, file_path: str, **kwargs):
        """
        发布文件事件
        
        Args:
            event_type: 事件类型
            file_path: 文件路径
            **kwargs: 额外参数
        """
        try:
            file_path_obj = Path(file_path)
            file_name = file_path_obj.name
            
            # 获取文件大小（如果文件存在）
            file_size = 0
            if os.path.exists(file_path):
                try:
                    file_size = os.path.getsize(file_path)
                except OSError:
                    pass
            
            # 创建并发布事件
            if event_type == EventType.FILE_CREATED:
                event = FileWatcherEvent.file_created(
                    file_path=str(file_path),
                    file_name=file_name,
                    file_size=file_size
                )
            elif event_type == EventType.FILE_MODIFIED:
                event = FileWatcherEvent.file_modified(
                    file_path=str(file_path),
                    file_name=file_name,
                    file_size=file_size
                )
            elif event_type == EventType.FILE_DELETED:
                event = FileWatcherEvent.file_deleted(
                    file_path=str(file_path),
                    file_name=file_name
                )
            elif event_type == EventType.FILE_MOVED:
                src_path = kwargs.get("src_path", "")
                dest_path = kwargs.get("dest_path", file_path)
                src_name = Path(src_path).name if src_path else file_name
                dest_name = Path(dest_path).name if dest_path else file_name
                event = FileWatcherEvent.file_moved(
                    src_path=src_path,
                    dest_path=dest_path,
                    src_name=src_name,
                    dest_name=dest_name
                )
            else:
                return
                
            self.event_bus.publish(event)
            logger.info(f"发布文件事件: {event_type.value} - {file_name}")
            
        except Exception as e:
            logger.error(f"发布文件事件失败: {e}")
            
    def on_created(self, event: FileCreatedEvent):
        """文件创建事件处理"""
        if not event.is_directory:
            file_path = str(event.src_path)  # 转换为字符串
            if self._should_process(file_path) and self._debounce_event(file_path):
                logger.info(f"检测到新文件: {file_path}")
                self._publish_file_event(EventType.FILE_CREATED, file_path)
                
    def on_modified(self, event: FileModifiedEvent):
        """文件修改事件处理"""
        if not event.is_directory:
            file_path = str(event.src_path)  # 转换为字符串
            if self._should_process(file_path) and self._debounce_event(file_path):
                logger.info(f"检测到文件修改: {file_path}")
                self._publish_file_event(EventType.FILE_MODIFIED, file_path)
                
    def on_deleted(self, event: FileDeletedEvent):
        """文件删除事件处理"""
        if not event.is_directory:
            file_path = str(event.src_path)  # 转换为字符串
            if self._debounce_event(file_path):
                logger.info(f"检测到文件删除: {file_path}")
                self._publish_file_event(EventType.FILE_DELETED, file_path)
                
    def on_moved(self, event: FileMovedEvent):
        """文件移动事件处理"""
        if not event.is_directory:
            src_path = str(event.src_path)  # 转换为字符串
            dest_path = str(event.dest_path)  # 转换为字符串
            
            # 检查目标文件是否支持
            if self._should_process(dest_path) and self._debounce_event(dest_path):
                logger.info(f"检测到文件移动: {src_path} -> {dest_path}")
                self._publish_file_event(
                    EventType.FILE_MOVED,
                    dest_path,
                    src_path=src_path,
                    dest_path=dest_path
                )


class FileWatcherService:
    """文件监控服务"""
    
    _instance: Optional["FileWatcherService"] = None
    
    def __new__(cls):
        """实现单例模式"""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
        
    def __init__(self):
        """初始化文件监控服务"""
        if self._initialized:
            return
            
        self.config = get_config_manager()
        self.event_bus = EventBus.get_instance()
        self.observer = None  # type: ignore
        self.event_handler: Optional[KnowledgeBaseEventHandler] = None
        self.watch_thread: Optional[Thread] = None
        self.stop_event = ThreadEvent()
        self.is_running = False
        self.watch_dir: Optional[Path] = None
        
        self._initialized = True
        logger.debug("文件监控服务已初始化")
        
    @classmethod
    def get_instance(cls) -> "FileWatcherService":
        """获取单例实例"""
        return cls()
        
    def start(self, watch_dir: Optional[str] = None) -> bool:
        """
        启动文件监控
        
        Args:
            watch_dir: 监控目录路径，如果为None则使用配置中的知识库目录
            
        Returns:
            bool: 启动是否成功
        """
        if self.is_running:
            logger.warning("文件监控服务已经在运行")
            return True
            
        try:
            # 确定监控目录
            if watch_dir:
                self.watch_dir = Path(watch_dir)
            else:
                self.watch_dir = self.config.paths.knowledge_dir
                
            if not self.watch_dir or not self.watch_dir.exists():
                logger.error(f"监控目录不存在: {self.watch_dir}")
                return False
                
            # 创建目录（如果不存在）
            self.watch_dir.mkdir(parents=True, exist_ok=True)
            
            # 初始化事件处理器和观察者
            self.event_handler = KnowledgeBaseEventHandler(self.config, self.event_bus)
            self.observer = Observer()
            self.observer.schedule(self.event_handler, str(self.watch_dir), recursive=True)
            
            # 启动观察者线程
            self.stop_event.clear()
            self.observer.start()
            self.is_running = True
            
            # 发布监控启动事件
            self.event_bus.publish(
                FileWatcherEvent.watcher_started(str(self.watch_dir))
            )
            
            logger.info(f"文件监控服务已启动，监控目录: {self.watch_dir}")
            logger.info(f"支持的文件类型: {', '.join(self.event_handler.supported_extensions)}")
            
            return True
            
        except Exception as e:
            logger.error(f"启动文件监控服务失败: {e}")
            self.event_bus.publish(
                FileWatcherEvent.watcher_error(str(e), str(self.watch_dir) if self.watch_dir else "")
            )
            return False
            
    def stop(self) -> bool:
        """
        停止文件监控
        
        Returns:
            bool: 停止是否成功
        """
        if not self.is_running or not self.observer:
            logger.warning("文件监控服务未运行")
            return True
            
        try:
            # 停止观察者
            self.observer.stop()
            self.observer.join(timeout=5)
            self.is_running = False
            
            # 发布监控停止事件
            if self.watch_dir:
                self.event_bus.publish(
                    FileWatcherEvent.watcher_stopped(str(self.watch_dir))
                )
                
            logger.info("文件监控服务已停止")
            return True
            
        except Exception as e:
            logger.error(f"停止文件监控服务失败: {e}")
            return False
            
    def restart(self) -> bool:
        """
        重启文件监控服务
        
        Returns:
            bool: 重启是否成功
        """
        logger.info("重启文件监控服务...")
        
        if self.is_running:
            if not self.stop():
                return False
                
        return self.start()
        
    def get_status(self) -> Dict[str, Any]:
        """
        获取监控服务状态
        
        Returns:
            dict: 状态信息
        """
        return {
            "is_running": self.is_running,
            "watch_dir": str(self.watch_dir) if self.watch_dir else None,
            "supported_extensions": list(self.event_handler.supported_extensions) if self.event_handler else [],
            "observer_alive": self.observer.is_alive() if self.observer else False
        }
        
    def scan_existing_files(self) -> List[str]:
        """
        扫描目录中已存在的文件
        
        Returns:
            List[str]: 已存在的文件路径列表
        """
        if not self.watch_dir or not self.watch_dir.exists():
            return []
            
        existing_files = []
        for ext in self.event_handler.supported_extensions if self.event_handler else []:
            for file_path in self.watch_dir.rglob(f"*{ext}"):
                if file_path.is_file():
                    existing_files.append(str(file_path))
                    
        logger.info(f"扫描到 {len(existing_files)} 个已存在的文件")
        return existing_files


# 便捷函数
def get_file_watcher() -> FileWatcherService:
    """获取文件监控服务实例"""
    return FileWatcherService.get_instance()


def start_file_watcher(watch_dir: Optional[str] = None) -> bool:
    """启动文件监控服务"""
    return get_file_watcher().start(watch_dir)


def stop_file_watcher() -> bool:
    """停止文件监控服务"""
    return get_file_watcher().stop()


def restart_file_watcher() -> bool:
    """重启文件监控服务"""
    return get_file_watcher().restart()