#!/usr/bin/env python3
"""
文件监视器 - 自动化音频处理
监视目录中的新音频文件并自动处理它们。
支持多种用户模式：
1. 仅管理员：文件仅分配给管理员用户
2. 用户特定目录：每个用户有自己的文件夹（例如 /auto-process/user-uuid/）
3. 单一默认用户：所有文件分配给一个指定的用户
"""

import os
import time
import threading
import logging
from datetime import datetime
from pathlib import Path
import mimetypes
import subprocess
from werkzeug.utils import secure_filename

# Flask 应用组件将在函数内部导入以避免循环导入

class FileMonitor:
    def __init__(self, app, base_watch_directory, check_interval=30, mode='admin_only'):
        """
        初始化文件监视器。
        
        参数:
            app: Flask 应用实例（用于在后台线程中创建应用上下文）
            base_watch_directory (str): 监视新文件的基础目录
            check_interval (int): 检查新文件的频率（秒）
            mode (str): 处理模式 - 'admin_only', 'user_directories', 或 'single_user'
        """
        self.app = app
        self.base_watch_directory = Path(base_watch_directory)
        self.check_interval = check_interval
        self.mode = mode
        self.running = False
        self.thread = None
        
        # 确保基础目录存在
        self.base_watch_directory.mkdir(parents=True, exist_ok=True)
        
        # 设置日志
        self.logger = logging.getLogger('file_monitor')
        self.logger.setLevel(logging.INFO)
        
        # 支持的音频文件扩展名
        self.supported_extensions = {
            '.wav', '.mp3', '.flac', '.amr', '.3gp', '.3gpp', 
            '.m4a', '.aac', '.ogg', '.wma', '.webm', '.mp4', '.mov'
        }
        
        # 管理员用户和有效用户的缓存
        self._admin_user_id = None
        self._valid_users = {}  # 映射 user_id 到 username
        self._username_to_id = {}  # 映射 username 到 user_id
        self._last_user_cache_update = 0
        
    def start(self):
        """在后台线程中启动文件监视。"""
        if self.running:
            self.logger.warning("文件监视器已在运行")
            return
            
        self.running = True
        self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.thread.start()
        self.logger.info(f"文件监视器以 '{self.mode}' 模式启动，监视目录: {self.base_watch_directory}")
        
    def stop(self):
        """停止文件监视。"""
        self.running = False
        if self.thread:
            self.thread.join(timeout=5)
        self.logger.info("文件监视器已停止")
        
    def _update_user_cache(self):
        """更新有效用户和管理员用户的缓存。"""
        current_time = time.time()
        # 每5分钟更新一次缓存
        if current_time - self._last_user_cache_update < 300:
            return
        
        from src.clients import db
        from src.models import User
            
        with self.app.app_context():
            try:
                # 查找管理员用户
                admin_user = User.query.filter_by(is_admin=True).first()
                self._admin_user_id = admin_user.id if admin_user else None
                
                # 缓存所有有效用户
                users = User.query.all()
                self._valid_users = {user.id: user.username for user in users}
                self._username_to_id = {user.username: user.id for user in users}
                
                self._last_user_cache_update = current_time
                self.logger.debug(f"更新用户缓存: {len(self._valid_users)} 个用户，管理员: {self._admin_user_id}")
                
            except Exception as e:
                self.logger.error(f"更新用户缓存时出错: {e}")
        
    def _monitor_loop(self):
        """主监视循环。"""
        while self.running:
            try:
                self._update_user_cache()
                
                if self.mode == 'admin_only':
                    self._scan_admin_directory()
                elif self.mode == 'user_directories':
                    self._scan_user_directories()
                elif self.mode == 'single_user':
                    self._scan_single_user_directory()
                    
            except Exception as e:
                self.logger.error(f"目录扫描期间出错: {e}", exc_info=True)
            
            # 等待下一次检查
            time.sleep(self.check_interval)
            
    def _scan_admin_directory(self):
        """为主目录中的文件扫描，作为管理员用户处理。"""
        if not self._admin_user_id:
            self.logger.warning("未找到管理员用户，跳过管理员目录扫描")
            return
            
        self._scan_directory_for_user(self.base_watch_directory, self._admin_user_id)
        
    def _scan_user_directories(self):
        """扫描用户特定的子目录。"""
        if not self.base_watch_directory.exists():
            return
            
        # 查找用户目录（例如 user-uuid1, user-uuid2）
        for item in self.base_watch_directory.iterdir():
            if not item.is_dir():
                continue
                
            # 从目录名中提取用户 ID
            user_id = self._extract_user_id_from_dirname(item.name)
            if user_id and user_id in self._valid_users:
                self._scan_directory_for_user(item, user_id)
            elif item.name.startswith('user-'):
                self.logger.warning(f"找到用户目录 '{item.name}' 但用户 ID {user_id} 无效")
                
    def _scan_single_user_directory(self):
        """为单个配置的用户扫描目录。"""
        default_username = os.environ.get('AUTO_PROCESS_DEFAULT_USERNAME')
        if not default_username:
            self.logger.warning("single_user 模式下未配置 AUTO_PROCESS_DEFAULT_USERNAME")
            return
            
        user_id = self._username_to_id.get(default_username)
        if user_id:
            self._scan_directory_for_user(self.base_watch_directory, user_id)
        else:
            self.logger.warning(f"配置的默认用户名 '{default_username}' 无效")
            
    def _scan_directory_for_user(self, directory, user_id):
        """为特定用户扫描特定目录中的文件。"""
        if not directory.exists():
            return
            
        for file_path in directory.iterdir():
            if not file_path.is_file():
                continue

            # 跳过隐藏文件、处理中的文件或不支持的文件
            if file_path.name.startswith('.') or file_path.suffix == '.processing':
                continue
            
            if file_path.suffix.lower() not in self.supported_extensions:
                continue

            # 检查文件是否仍在写入中（大小稳定性检查）
            try:
                if not self._is_file_stable(file_path):
                    continue
            except FileNotFoundError:
                # 文件可能在 iterdir() 后被其他工作进程获取
                continue

            self.logger.info(f"为用户 {user_id} 找到可能的音频文件: {file_path}")

            # --- 通过重命名进行原子锁定 ---
            processing_path = file_path.with_suffix(file_path.suffix + '.processing')
            
            try:
                file_path.rename(processing_path)
                self.logger.info(f"为 {file_path} 获取锁，重命名为 {processing_path}")
            except FileNotFoundError:
                self.logger.debug(f"无法为 {file_path} 获取锁，已被其他工作进程处理。")
                continue
            except Exception as e:
                self.logger.error(f"为 {file_path} 获取锁时出错: {e}")
                continue

            # --- 处理锁定的文件 ---
            try:
                self._process_file(processing_path, user_id)
            except Exception as e:
                self.logger.error(f"处理文件 {processing_path} 时出错: {e}", exc_info=True)
                # 如果处理失败，通过重命名回原来解锁文件
                try:
                    original_path = processing_path.with_suffix(processing_path.suffix.replace('.processing', ''))
                    processing_path.rename(original_path)
                    self.logger.info(f"处理错误后将文件 {processing_path} 解锁回 {original_path}。")
                except Exception as rename_err:
                    self.logger.error(f"严重: 错误后无法解锁文件 {processing_path}: {rename_err}")
                
    def _extract_user_id_from_dirname(self, dirname):
        """
        从目录名中提取用户 ID。
        
        预期格式: user-uuid, uuid
        
        参数:
            dirname (str): 目录名
            
        返回:
            str or None: 如果找到则返回用户 ID，否则返回 None
        """
        import re
        import uuid
        
        # 模式: user-uuid 或仅 uuid
        patterns = [
            r'^user-([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$',  # user-uuid
            r'^([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$'       # uuid
        ]
        
        for pattern in patterns:
            match = re.match(pattern, dirname, re.IGNORECASE)
            if match:
                try:
                    user_id_str = match.group(1)
                    # 验证是否为有效的 UUID
                    uuid.UUID(user_id_str)
                    return user_id_str
                except (ValueError, AttributeError):
                    continue
                    
        return None
        
    def _is_file_stable(self, file_path, stability_time=5):
        """
        检查文件是否稳定（未被写入）。
        
        参数:
            file_path (Path): 文件路径
            stability_time (int): 等待大小稳定的时间（秒）
            
        返回:
            bool: 如果文件看起来稳定则返回 True
        """
        try:
            initial_size = file_path.stat().st_size
            initial_mtime = file_path.stat().st_mtime
            
            # 等待一会儿再检查
            time.sleep(min(stability_time, 2))
            
            current_size = file_path.stat().st_size
            current_mtime = file_path.stat().st_mtime
            
            # 如果大小和修改时间未改变，则文件稳定
            return initial_size == current_size and initial_mtime == current_mtime
            
        except (OSError, FileNotFoundError):
            return False
            
    def _process_file(self, processing_path, user_id):
        """
        为特定用户处理单个锁定的音频文件。
        
        参数:
            processing_path (Path): 锁定的音频文件路径（例如 file.mp3.processing）
            user_id (str): 要分配录音的用户 ID（UUID 字符串）
        """
        from src.clients import db
        from src.models import Recording, User
        from src.services.transcription_service import transcribe_audio_task
        from src.services.summary_service import generate_summary_only_task, generate_title_task
        
        with self.app.app_context():
            try:
                # 验证用户存在
                user = db.session.get(User, user_id)
                if not user:
                    self.logger.error(f"数据库中未找到用户 ID {user_id}，文件: {processing_path}")
                    # 必须引发异常以触发解锁机制
                    raise ValueError(f"未找到用户 ID {user_id}")

                # 通过移除 .processing 后缀推导原始文件名
                original_filename = processing_path.name.replace('.processing', '')
                safe_filename = secure_filename(original_filename)
                timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
                new_filename = f"auto_{timestamp}_{safe_filename}"
                
                uploads_dir = Path(self.app.config['UPLOAD_FOLDER'])
                uploads_dir.mkdir(parents=True, exist_ok=True)
                destination_path = uploads_dir / new_filename
                
                # 将锁定文件复制到上传目录
                import shutil
                shutil.copy(str(processing_path), str(destination_path))
                self.logger.info(f"已将 {processing_path} 复制到 {destination_path}")
                
                # 成功复制后从监视目录中删除锁定文件
                try:
                    processing_path.unlink()
                    self.logger.info(f"已删除锁定文件: {processing_path}")
                except FileNotFoundError:
                    # 如果持有锁，这不应该发生，但最好记录
                    self.logger.warning(f"锁定文件 {processing_path} 已被删除。")
                
                # 如果需要，转换文件（与 upload_file 相同的逻辑）
                final_path = self._convert_file_if_needed(destination_path, original_filename)
                
                # 获取文件大小和 MIME 类型
                file_size = final_path.stat().st_size
                mime_type, _ = mimetypes.guess_type(str(final_path))
                
                # 创建数据库记录
                now = datetime.utcnow()
                recording = Recording(
                    audio_path=str(final_path),
                    original_filename=original_filename,
                    title=f"自动处理 - {original_filename}",
                    file_size=file_size,
                    status='PENDING',
                    meeting_date=now.date(),
                    user_id=user_id,  # 现在使用字符串 UUID
                    mime_type=mime_type,
                    is_inbox=True,  # 自动处理的文件进入收件箱
                    processing_source='auto_process'  # 跟踪这是自动处理的
                )
                
                db.session.add(recording)
                db.session.commit()
                
                self.logger.info(f"为用户 {user.username} 创建录音记录，ID: {recording.id}")
                
                # 启动后台处理
                start_time = datetime.utcnow()
                task_kwargs = {
                    'USE_ASR_ENDPOINT': self.app.config.get('USE_ASR_ENDPOINT', False),
                    'ASR_BASE_URL': self.app.config.get('ASR_BASE_URL', ''),
                    'ASR_DIARIZE': self.app.config.get('ASR_DIARIZE', False),
                    'chunking_service': self.app.config.get('chunking_service'),
                    'ENABLE_CHUNKING': self.app.config.get('ENABLE_CHUNKING', False),
                    'generate_title_task': generate_title_task,
                    'generate_summary_only_task': generate_summary_only_task,
                    'app_logger': self.app.logger,
                    'UPLOAD_FOLDER': self.app.config.get('UPLOAD_FOLDER')
                }
                thread = threading.Thread(
                    target=transcribe_audio_task,
                    args=(self.app.app_context(), recording.id, str(final_path), final_path.name, start_time),
                    kwargs=task_kwargs
                )
                thread.start()
                
                self.logger.info(f"为录音 ID {recording.id} 启动后台处理")
                self.logger.info(f"成功处理并移动文件从: {processing_path}")
                    
            except Exception as e:
                self.logger.error(f"为用户 {user_id} 处理文件 {processing_path} 时出错: {e}", exc_info=True)
                # 重新引发异常，由调用方法捕获，该方法将处理解锁。
                raise
                
    def _convert_file_if_needed(self, file_path, original_filename):
        """
        如果需要，将音频文件转换为支持的格式。
        使用 128kbps MP3 以获得最佳大小/质量平衡。
        
        参数:
            file_path (Path): 当前文件路径
            original_filename (str): 用于格式检测的原始文件名
            
        返回:
            Path: 最终（可能已转换的）文件的路径
        """
        filename_lower = original_filename.lower()
        
        # 在可能的情况下直接支持 WebM 和其他格式
        supported_formats = ('.wav', '.mp3', '.flac', '.webm', '.m4a', '.aac', '.ogg')
        convertible_formats = ('.amr', '.3gp', '.3gpp', '.wma', '.mp4', '.mov')
        
        if filename_lower.endswith(supported_formats):
            self.logger.info(f"文件格式 {filename_lower} 受支持，无需转换")
            return file_path
            
        if not filename_lower.endswith(convertible_formats):
            self.logger.warning(f"未知文件格式 {filename_lower}，仍尝试转换")
            
        # 需要转换为高质量 MP3 以获得更好的转录准确性
        self.logger.info(f"将 {filename_lower} 格式转换为高质量 MP3")
        
        base_path = file_path.with_suffix('')
        temp_mp3_path = base_path.with_suffix('.temp.mp3')
        final_mp3_path = base_path.with_suffix('.mp3')
        
        try:
            # 转换为高质量 MP3（128kbps, 44.1kHz）以获得更好的转录准确性
            subprocess.run([
                'ffmpeg', '-i', str(file_path), '-y', 
                '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', '-ac', '1', 
                str(temp_mp3_path)
            ], check=True, capture_output=True, text=True)
            
            self.logger.info(f"成功将 {file_path} 转换为 {temp_mp3_path} (128kbps MP3)")
            
            # 删除原始文件并重命名临时文件
            file_path.unlink()
            temp_mp3_path.rename(final_mp3_path)
            
            return final_mp3_path
            
        except FileNotFoundError:
            self.logger.error("未找到 ffmpeg。请确保已安装 ffmpeg。")
            raise
        except subprocess.CalledProcessError as e:
            self.logger.error(f"ffmpeg 转换失败: {e.stderr}")
            raise


# 全局文件监视器实例
file_monitor = None

def start_file_monitor(app=None):
    """使用环境变量中的配置启动文件监视器。"""
    global file_monitor
    
    if file_monitor and file_monitor.running:
        return
    
    # 如果未提供 app，尝试使用 current_app（仅当在应用上下文中调用时有效）
    if app is None:
        try:
            from flask import has_app_context, current_app
            if has_app_context():
                app = current_app._get_current_object()
            else:
                raise RuntimeError("不在应用上下文中")
        except Exception:
            logging.getLogger('file_monitor').error("无法获取 Flask 应用实例，请确保传入 app 参数")
            return
        
    # 从环境变量获取配置
    watch_dir = os.environ.get('AUTO_PROCESS_WATCH_DIR', '/data/auto-process')
    check_interval = int(os.environ.get('AUTO_PROCESS_CHECK_INTERVAL', '30'))
    mode = os.environ.get('AUTO_PROCESS_MODE', 'admin_only')  # admin_only, user_directories, single_user
    
    # 验证模式
    valid_modes = ['admin_only', 'user_directories', 'single_user']
    if mode not in valid_modes:
        app.logger.error(f"无效的 AUTO_PROCESS_MODE: {mode}。必须是以下之一: {valid_modes}")
        return
            
    # 仅在启用自动处理时启动
    if os.environ.get('ENABLE_AUTO_PROCESSING', 'false').lower() == 'true':
        file_monitor = FileMonitor(
            app=app,
            base_watch_directory=watch_dir,
            check_interval=check_interval,
            mode=mode
        )
        file_monitor.start()
        app.logger.info(f"自动化文件处理以 '{mode}' 模式启动")
    else:
        app.logger.info("自动化文件处理已禁用")

def stop_file_monitor():
    """停止文件监视器。"""
    global file_monitor
    if file_monitor:
        file_monitor.stop()
        file_monitor = None

def get_file_monitor_status():
    """获取文件监视器的当前状态。"""
    global file_monitor
    if file_monitor and file_monitor.running:
        return {
            'running': True,
            'mode': file_monitor.mode,
            'watch_directory': str(file_monitor.base_watch_directory),
            'check_interval': file_monitor.check_interval
        }
    else:
        return {'running': False}
