from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import os
from werkzeug.utils import secure_filename
import uuid
from datetime import datetime

# 导入处理模块
from processors.video_processor import VideoProcessor
from processors.pdf_processor import PDFProcessor
from processors.audio_processor import AudioProcessor
from processors.concept_extractor import ConceptExtractor
from processors.subtitle_processor import SubtitleProcessor
from processors.image_alignment import ImageAlignmentProcessor

app = Flask(__name__, static_folder='.', static_url_path='')
CORS(app)  # 允许跨域请求

# 配置
UPLOAD_FOLDER = 'uploads'
PROCESSED_FOLDER = 'processed'
ALLOWED_VIDEO_EXTENSIONS = {'mp4', 'avi', 'mov', 'mkv'}
ALLOWED_PDF_EXTENSIONS = {'pdf'}
MAX_FILE_SIZE = 500 * 1024 * 1024  # 500MB

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['PROCESSED_FOLDER'] = PROCESSED_FOLDER
app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE

# 创建必要的文件夹
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(PROCESSED_FOLDER, exist_ok=True)
os.makedirs(os.path.join(PROCESSED_FOLDER, 'videos'), exist_ok=True)
os.makedirs(os.path.join(PROCESSED_FOLDER, 'images'), exist_ok=True)
os.makedirs(os.path.join(PROCESSED_FOLDER, 'audio'), exist_ok=True)
os.makedirs(os.path.join(PROCESSED_FOLDER, 'subtitles'), exist_ok=True)

# 初始化处理器
video_processor = VideoProcessor()
pdf_processor = PDFProcessor()
audio_processor = AudioProcessor()
concept_extractor = ConceptExtractor()
subtitle_processor = SubtitleProcessor()
image_alignment_processor = ImageAlignmentProcessor()

def allowed_file(filename, allowed_extensions):
    """检查文件扩展名是否允许"""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions

def align_concepts_with_videos(concepts, video_segments):
    """
    将概念与视频片段进行对齐
    
    Args:
        concepts: 概念列表，包含时间戳 [{'term': '细胞', 'timestamps': ['00:00-00:18'], ...}]
        video_segments: 视频片段列表 [{'start_time': 0, 'end_time': 18, ...}]
    
    Returns:
        dict: 概念-视频映射 {'细胞': [video_segment1, video_segment2], ...}
    """
    concept_video_mapping = {}
    
    for concept in concepts:
        term = concept.get('term', '')
        timestamps = concept.get('timestamps', [])
        
        if not term or not timestamps:
            continue
        
        matched_videos = []
        
        # 遍历每个时间戳
        for timestamp in timestamps:
            # 解析时间戳（格式：00:00-00:18）
            try:
                if '-' in timestamp:
                    start_str, end_str = timestamp.split('-')
                    start_seconds = time_to_seconds(start_str.strip())
                    end_seconds = time_to_seconds(end_str.strip())
                else:
                    continue
                
                # 查找匹配的视频片段
                for video in video_segments:
                    video_start = video.get('start_time', 0)
                    video_end = video.get('end_time', 0)
                    
                    # 检查时间段是否有重叠
                    if (start_seconds < video_end and end_seconds > video_start):
                        # 避免重复添加
                        if video not in matched_videos:
                            matched_videos.append(video)
            
            except Exception as e:
                print(f"      ⚠️  解析时间戳失败 {timestamp}: {str(e)}")
                continue
        
        if matched_videos:
            concept_video_mapping[term] = matched_videos
    
    return concept_video_mapping

def time_to_seconds(time_str):
    """将时间字符串转换为秒数 (MM:SS 或 HH:MM:SS)"""
    parts = time_str.split(':')
    if len(parts) == 2:  # MM:SS
        return int(parts[0]) * 60 + int(parts[1])
    elif len(parts) == 3:  # HH:MM:SS
        return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])
    else:
        return 0

@app.route('/')
def index():
    """返回主页面"""
    return send_from_directory('.', 'main.html')

@app.route('/graph.png')
def serve_graph():
    """提供知识图谱可视化图片"""
    return send_from_directory('.', 'graph.png')

@app.route('/example/<filename>')
def serve_example(filename):
    """提供example文件夹中的示例文件"""
    return send_from_directory('example', filename)

@app.route('/upload', methods=['POST'])
def upload_files():
    """处理文件上传"""
    print("\n" + "=" * 80)
    print("📥 接收到上传请求")
    print("=" * 80)
    
    try:
        print("🔍 检查上传的文件...")
        print(f"   request.files: {list(request.files.keys())}")
        
        # 检查是否有文件
        if 'video' not in request.files and 'pdf' not in request.files:
            print("❌ 错误：没有找到文件")
            return jsonify({'error': '没有上传文件'}), 400
        
        video_file = request.files.get('video')
        pdf_file = request.files.get('pdf')
        
        print(f"   视频文件: {video_file.filename if video_file else 'None'}")
        print(f"   PDF文件: {pdf_file.filename if pdf_file else 'None'}")
        
        # 生成唯一的处理ID
        process_id = str(uuid.uuid4())
        print(f"   生成处理ID: {process_id}")
        
        results = {
            'process_id': process_id,
            'timestamp': datetime.now().isoformat(),
            'video_segments': None,
            'images': None,
            'audio': None,
            'concepts': None,
            'subtitles': None
        }
        
        video_path = None
        timestamps = None
        
        # 处理视频文件
        if video_file and video_file.filename:
            print(f"\n📹 开始处理视频文件: {video_file.filename}")
            print("=" * 80)
            
            if not allowed_file(video_file.filename, ALLOWED_VIDEO_EXTENSIONS):
                print(f"❌ 不支持的视频格式: {video_file.filename}")
                return jsonify({'error': '不支持的视频格式'}), 400
            
            # 保存视频文件
            try:
                video_filename = secure_filename(f"{process_id}_{video_file.filename}")
                video_path = os.path.join(app.config['UPLOAD_FOLDER'], video_filename)
                print(f"   💾 保存视频到: {video_path}")
                video_file.save(video_path)
                print(f"   ✅ 视频保存成功！")
            except Exception as e:
                print(f"   ❌ 保存视频失败: {str(e)}")
                raise
            
            # 步骤1: 提取音频并使用Whisper生成字幕
            print(f"\n{'='*80}")
            print("🎵 步骤1: 提取音频并生成字幕（Whisper API）")
            print("=" * 80)
            
            try:
                audio_results = audio_processor.extract_audio(video_path, process_id)
                results['audio'] = audio_results
                print(f"   ✅ 音频处理完成")
                if audio_results.get('transcription'):
                    print(f"   📝 获得 {len(audio_results['transcription'])} 个字幕片段")
                else:
                    print(f"   ⚠️  未获得字幕转录")
            except Exception as e:
                print(f"   ❌ 音频处理失败: {str(e)}")
                import traceback
                traceback.print_exc()
                raise
            
            # 步骤2: 使用LLM进行语义分段
            if audio_results and audio_results.get('transcription'):
                print(f"\n{'='*80}")
                print("🤖 步骤2: 使用LLM进行语义分段")
                print("=" * 80)
                
                try:
                    # 创建字幕文本
                    print("   📄 创建字幕文本...")
                    subtitle_text = subtitle_processor.create_subtitle_from_transcription(
                        audio_results['transcription']
                    )
                    print(f"   ✅ 字幕文本创建完成，长度: {len(subtitle_text)} 字符")
                    
                    # 使用LLM进行语义分段
                    print("   🔄 调用LLM进行语义分段...")
                    semantic_result = subtitle_processor.semantic_integration_with_llm(subtitle_text)
                    
                    # 保存语义分段结果到txt文件
                    semantic_output_dir = os.path.join('processed', 'subtitles', process_id)
                    os.makedirs(semantic_output_dir, exist_ok=True)
                    semantic_filename = f"{process_id}_semantic_result.txt"
                    semantic_file_path = os.path.join(semantic_output_dir, semantic_filename)
                    
                    try:
                        # 保存LLM语义分段结果
                        with open(semantic_file_path, 'w', encoding='utf-8') as f:
                            f.write(semantic_result)
                        print(f"   💾 语义分段结果已保存到: {semantic_filename}")
                        
                        # 解析LLM返回的时间戳
                        print("   📊 解析LLM返回结果...")
                        timestamps = subtitle_processor.parse_timestamps(semantic_result)
                        print(f"   ✅ LLM语义分段成功，生成 {len(timestamps)} 个语义段")
                    except:
                        print("   ❌ 语义分段失败")

                        

                    
                    # 格式化字幕用于显示
                    print("   📝 格式化字幕...")
                    subtitles = subtitle_processor.format_subtitle_for_display(timestamps)
                    results['subtitles'] = subtitles
                    print(f"   ✅ 字幕格式化完成")
                    
                except Exception as e:
                    print(f"   ❌ 语义分段失败: {str(e)}")
                    import traceback
                    traceback.print_exc()
                    raise
            else:
                print(f"\n⚠️  未获取到转录文本，将使用固定时长分割")
            
            # 步骤3: 根据语义时间戳切割视频
            print("\n📋 步骤3: 根据语义时间戳切割视频")
            print("=" * 60)
            
            try:
                if timestamps:
                    print(f"   使用语义时间戳切割，共 {len(timestamps)} 个分段")
                else:
                    print(f"   使用固定时长切割")
                    
                video_results = video_processor.process(video_path, process_id, timestamps)
                results['video_segments'] = video_results['segments']
                
                print(f"   ✅ 视频处理完成，生成 {len(video_results['segments'])} 个片段")
                
            except Exception as e:
                print(f"   ❌ 步骤3失败: {str(e)}")
                import traceback
                traceback.print_exc()
                raise
        
        # 处理PDF文件
        if pdf_file and pdf_file.filename:
            print("\n📄 开始处理PDF文件")
            print("-" * 60)
            
            if not allowed_file(pdf_file.filename, ALLOWED_PDF_EXTENSIONS):
                print(f"❌ 错误: 不支持的PDF格式 {pdf_file.filename}")
                return jsonify({'error': '不支持的PDF格式'}), 400
            
            # 保存PDF文件
            pdf_filename = secure_filename(f"{process_id}_{pdf_file.filename}")
            pdf_path = os.path.join(app.config['UPLOAD_FOLDER'], pdf_filename)
            
            print(f"   保存路径: {pdf_path}")
            print(f"   正在保存PDF文件...")
            
            try:
                pdf_file.save(pdf_path)
                file_size = os.path.getsize(pdf_path)
                print(f"   ✅ PDF文件保存成功! 大小: {file_size / 1024 / 1024:.2f} MB")
            except Exception as e:
                print(f"   ❌ 保存PDF失败: {str(e)}")
                raise
            
            print(f"\n{'='*60}")
            print(f"📄 步骤4: 处理PDF文件 (使用API提取图片)")
            print("=" * 60)
            print(f"   📁 PDF文件名: {pdf_filename}")
            print(f"   🆔 Process ID: {process_id}")
            print(f"   📂 输出目录: processed/images/{process_id}/")
            
            # 处理PDF（使用API提取图片）
            try:
                pdf_results = pdf_processor.process(pdf_path, process_id)
                results['images'] = pdf_results['images']
                
                print(f"   ✅ PDF图片提取完成，共 {len(pdf_results['images'])} 张图片")
                
            except Exception as e:
                print(f"   ❌ PDF处理失败: {str(e)}")
                import traceback
                traceback.print_exc()
                raise
            
            # 提取概念（使用三阶段NER方法，从视频字幕提取）
            try:
                print("\n📋 提取关键概念 (三阶段NER - 从视频字幕)")
                print("=" * 60)
                print("   📹 数据来源: 视频字幕 (Whisper转录 → 语义分段)")
                print("   🌐 API提供商: https://jeniya.cn/")
                
                # 初始化概念提取器（使用与subtitle_processor相同的API配置）
                concept_extractor_new = ConceptExtractor(
                    subject="生物学",  # 可以根据需要修改学科
                    api_key=os.environ.get('SILICONFLOW_API_KEY'),
                    base_url=os.environ.get('SILICONFLOW_BASE_URL', 'https://jeniya.cn/')
                )
                
                # 准备视频字幕数据用于概念提取
                subtitle_input = None
                if timestamps:
                    # 从语义分段后的字幕准备输入
                    subtitle_input = []
                    for key, data in timestamps.items():
                        subtitle_input.append({
                            'timestamp': data.get('timestamp_str', ''),
                            'text': data.get('content', '')
                        })
                    print(f"   📝 从视频字幕准备了 {len(subtitle_input)} 个语义段用于NER提取")
                
                # 执行三阶段概念提取
                concepts = concept_extractor_new.extract_concepts(
                    subtitle_data=subtitle_input,
                    pdf_text=None,  # 可选：传入PDF文本
                    process_id=process_id,
                    k=3  # Self-Refine迭代次数
                )
                
                if concepts and concepts.get('total', 0) > 0:
                    print(f"   ✅ 概念提取完成，发现 {concepts.get('total', 0)} 个关键概念")
                    
                    # 为概念生成解释（参考 NER.py 的 expandExplanations）
                    print("\n📖 步骤6: 为概念生成解释")
                    print("=" * 60)
                    
                    try:
                        concepts_list = concepts.get('concepts', [])
                        explanations = concept_extractor_new.generate_explanations(
                            concepts_list=concepts_list,
                            process_id=process_id
                        )
                        
                        print(f"   ✅ 概念解释生成完成，共 {len(explanations)} 个")
                        
                        # 为概念解释生成音频（参考 Speechgenerator.py）
                        print("\n🔊 步骤7: 为概念生成音频")
                        print("=" * 60)
                        
                        try:
                            explanations_with_audio = concept_extractor_new.generate_audio_for_concepts(
                                explanations=explanations,
                                process_id=process_id
                            )
                            
                            # 将带音频的解释添加到结果中
                            concepts['explanations'] = explanations_with_audio
                            
                            success_count = sum(1 for e in explanations_with_audio if e.get('audio_path'))
                            print(f"   ✅ 概念音频生成完成: {success_count}/{len(explanations_with_audio)} 个")
                            
                        except Exception as e:
                            print(f"   ❌ 概念音频生成失败: {str(e)}")
                            import traceback
                            traceback.print_exc()
                            # 如果音频生成失败，至少保留解释
                            concepts['explanations'] = explanations
                        
                    except Exception as e:
                        print(f"   ❌ 概念解释生成失败: {str(e)}")
                        import traceback
                        traceback.print_exc()
                        concepts['explanations'] = []
                else:
                    print(f"   ⚠️  未提取到概念")
                
                results['concepts'] = concepts
                
                # 概念-视频对齐：将概念与视频片段进行匹配
                if concepts and concepts.get('total', 0) > 0 and results.get('video_segments'):
                    try:
                        print("\n🎬 步骤8: 概念与视频对齐")
                        print("=" * 60)
                        
                        # 准备概念数据（包含时间戳）
                        concepts_with_timestamps = []
                        if concepts.get('explanations'):
                            for item in concepts['explanations']:
                                term = item['term']
                                # 从原始concepts列表中找到对应的时间戳
                                for concept in concepts.get('concepts', []):
                                    if concept.get('term') == term:
                                        concepts_with_timestamps.append({
                                            'term': term,
                                            'explanation': item.get('explanation', ''),
                                            'audio_path': item.get('audio_path', ''),
                                            'timestamps': concept.get('timestamps', [])
                                        })
                                        break
                        elif concepts.get('concepts'):
                            concepts_with_timestamps = concepts['concepts']
                        
                        # 执行概念-视频对齐
                        concept_video_mapping = align_concepts_with_videos(
                            concepts_with_timestamps, 
                            results['video_segments']
                        )
                        
                        # 将对齐结果添加到返回数据
                        results['concept_videos'] = concept_video_mapping
                        
                        aligned_count = len(concept_video_mapping)
                        total_videos = sum(len(vids) for vids in concept_video_mapping.values())
                        print(f"   ✅ 概念-视频对齐完成: {aligned_count} 个概念匹配 {total_videos} 个视频片段")
                        
                    except Exception as e:
                        print(f"   ❌ 概念-视频对齐失败: {str(e)}")
                        import traceback
                        traceback.print_exc()
                        results['concept_videos'] = {}
                
                # 图像对齐：将PDF图像与提取的概念进行匹配
                if concepts and concepts.get('total', 0) > 0 and results.get('images'):
                    try:
                        print("\n🖼️  步骤9: 图像与概念对齐")
                        print("=" * 60)
                        print(f"   📚 PDF图像: {len(results['images'])} 张")
                        print(f"   📋 提取概念: {concepts.get('total', 0)} 个")
                        
                        # 准备概念列表（从explanations或concepts中获取）
                        concepts_list = []
                        if concepts.get('explanations'):
                            concepts_list = [item['term'] for item in concepts['explanations']]
                        elif concepts.get('concepts'):
                            concepts_list = concepts['concepts']
                        
                        # 准备图像路径列表
                        image_paths = results['images']
                        
                        # 执行图像对齐
                        concept_image_mapping = image_alignment_processor.process(
                            image_paths=image_paths,
                            concepts=concepts_list,
                            process_id=process_id
                        )
                        
                        # 将对齐结果添加到返回数据
                        results['concept_images'] = concept_image_mapping
                        
                        aligned_count = len(concept_image_mapping)
                        total_images = sum(len(imgs) for imgs in concept_image_mapping.values())
                        print(f"   ✅ 图像对齐完成: {aligned_count} 个概念匹配 {total_images} 张图像")
                        
                    except Exception as e:
                        print(f"   ❌ 图像对齐失败: {str(e)}")
                        import traceback
                        traceback.print_exc()
                        # 图像对齐失败不影响主流程
                        results['concept_images'] = {}
                    
            except Exception as e:
                print(f"   ❌ 概念提取失败: {str(e)}")
                import traceback
                traceback.print_exc()
                # 概念提取失败不影响主流程
                results['concepts'] = {'concepts': [], 'total': 0}
        
        # 过滤概念：只显示指定的7个概念
        DISPLAY_CONCEPTS = [
            '上皮组织',
            '动物细胞',
            '受精卵',
            '神经组织',
            '神经细胞',
            '细胞分化',
            '肌细胞',
            '肌肉组织'
        ]
        
        if results.get('concepts') and results['concepts'].get('concepts'):
            print("\n🔍 过滤概念：只展示指定的7个概念")
            original_count = len(results['concepts']['concepts'])
            
            # 过滤concepts列表
            filtered_concepts = [
                concept for concept in results['concepts']['concepts']
                if concept.get('term') in DISPLAY_CONCEPTS
            ]
            results['concepts']['concepts'] = filtered_concepts
            results['concepts']['total'] = len(filtered_concepts)
            
            # 过滤explanations列表
            if results['concepts'].get('explanations'):
                filtered_explanations = [
                    exp for exp in results['concepts']['explanations']
                    if exp.get('term') in DISPLAY_CONCEPTS
                ]
                results['concepts']['explanations'] = filtered_explanations
            
            # 过滤concept_videos
            if results.get('concept_videos'):
                filtered_concept_videos = {
                    term: videos for term, videos in results['concept_videos'].items()
                    if term in DISPLAY_CONCEPTS
                }
                results['concept_videos'] = filtered_concept_videos
            
            # 过滤concept_images
            if results.get('concept_images'):
                filtered_concept_images = {
                    term: images for term, images in results['concept_images'].items()
                    if term in DISPLAY_CONCEPTS
                }
                results['concept_images'] = filtered_concept_images
            
            print(f"   原始概念数: {original_count} 个")
            print(f"   过滤后概念数: {len(filtered_concepts)} 个")
            print(f"   展示概念: {', '.join([c.get('term', '') for c in filtered_concepts])}")
        
        print("\n" + "=" * 80)
        print("✅ 所有处理完成！")
        print("=" * 80)
        print(f"\n处理结果摘要:")
        print(f"  - 视频片段: {len(results.get('video_segments', [])) if results.get('video_segments') else 0} 个")
        print(f"  - PDF图片: {len(results.get('images', [])) if results.get('images') else 0} 张")
        print(f"  - 关键概念: {len(results.get('concepts', {}).get('concepts', [])) if results.get('concepts') else 0} 个")
        print(f"  - 概念解释: {len(results.get('concepts', {}).get('explanations', [])) if results.get('concepts') else 0} 个")
        print(f"  - 概念音频: {sum(1 for e in results.get('concepts', {}).get('explanations', []) if e.get('audio_path'))} 个")
        print(f"  - 概念-视频对齐: {len(results.get('concept_videos', {}))} 个概念, {sum(len(vids) for vids in results.get('concept_videos', {}).values())} 个视频片段")
        print(f"  - 概念-图像对齐: {len(results.get('concept_images', {}))} 个概念, {sum(len(imgs) for imgs in results.get('concept_images', {}).values())} 张图像")
        print(f"  - 字幕: {'✓' if results.get('subtitles') else '✗'}")
        print("=" * 80 + "\n")
        
        return jsonify(results), 200
        
    except Exception as e:
        print("\n" + "=" * 80)
        print("❌ 处理过程中发生错误！")
        print("=" * 80)
        print(f"错误类型: {type(e).__name__}")
        print(f"错误信息: {str(e)}")
        print("\n完整错误堆栈:")
        import traceback
        traceback.print_exc()
        print("=" * 80 + "\n")
        return jsonify({'error': f'处理失败: {str(e)}'}), 500

@app.route('/processed/<path:filename>')
def serve_processed_file(filename):
    """提供处理后的文件"""
    return send_from_directory(app.config['PROCESSED_FOLDER'], filename)

@app.route('/status/<process_id>')
def get_status(process_id):
    """获取处理状态"""
    # 可以实现一个进度跟踪系统
    return jsonify({'status': 'processing', 'progress': 50})

if __name__ == '__main__':
    print("\n" + "=" * 60)
    print("  SciMKG 自动化构建系统")
    print("=" * 60)
    print("\n处理流程：")
    print("  1. 视频 → 提取音频 → Whisper生成字幕")
    print("  2. 字幕 → LLM语义分段（或规则分段）")
    print("  3. 语义时间戳 → 智能切割视频")
    print("  4. PDF → 提取图片和文本")
    print("  5. 综合提取关键概念")
    print("\n" + "=" * 60)
    print("  服务器启动中...")
    print("  访问地址: http://localhost:5000")
    print("=" * 60 + "\n")
    
    app.run(debug=True, host='0.0.0.0', port=5000)

