"""
YouTube 视频搜索脚本 - 按主题搜索、评分、生成日报
从 themes.yaml 读取主题列表，对每个主题分别搜索 YouTube
合并结果、评分、排序后生成日报报告
"""

import sys
import os
import json
import argparse
import re
from pathlib import Path
from datetime import datetime, timedelta, timezone

# 设置控制台编码为UTF-8（Windows）
if sys.platform == 'win32':
    import io
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

try:
    import httpx
except ImportError:
    print("❌ 错误: 需要安装 httpx 库")
    print("💡 运行: pip install httpx")
    sys.exit(1)

try:
    import yaml
except ImportError:
    print("❌ 错误: 需要安装 PyYAML 库")
    print("💡 运行: pip install pyyaml")
    sys.exit(1)

# 加载 .env 文件（如果存在）
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass  # dotenv 可选，如果未安装则跳过

# 可选：导入 LLM 相关模块（仅用于 research 模式）
try:
    from hello_agents.core.llm import HelloAgentsLLM
    LLM_AVAILABLE = True
except ImportError:
    LLM_AVAILABLE = False

# 配置常量
DAYS_WINDOW = int(os.getenv("DAYS_WINDOW", "14"))  # 时间窗口：默认14天


def load_youtube_api_key():
    """从环境变量或配置文件中加载 YouTube API Key"""
    # 首先尝试环境变量
    api_key = os.getenv("YOUTUBE_API_KEY")
    
    if api_key:
        return api_key
    
    # 尝试从配置文件中读取
    config_file = Path(__file__).parent / "config"
    if config_file.exists():
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if line.startswith("YOUTUBE_API_KEY=") and not line.startswith("#"):
                        api_key = line.split("=", 1)[1].strip()
                        if api_key:
                            return api_key
        except Exception as e:
            print(f"⚠️  读取配置文件失败: {e}")
    
    return None


def load_themes():
    """从 themes.yaml 读取主题列表"""
    themes_file = Path(__file__).parent / "themes.yaml"
    if not themes_file.exists():
        print(f"❌ 错误: 找不到 themes.yaml 文件: {themes_file}")
        return []
    
    try:
        with open(themes_file, 'r', encoding='utf-8') as f:
            data = yaml.safe_load(f)
            if data is None:
                print(f"❌ 错误: themes.yaml 文件为空或格式错误")
                return []
            themes = data.get('themes', [])
            if not themes:
                print(f"⚠️  警告: themes.yaml 中未找到主题列表")
                return []
            print(f"✅ 加载了 {len(themes)} 个主题: {', '.join(themes)}")
            return themes
    except Exception as e:
        print(f"❌ 读取 themes.yaml 失败: {e}")
        import traceback
        traceback.print_exc()
        return []


def load_whitelist_channels():
    """从 channels.yaml 读取白名单频道"""
    channels_file = Path(__file__).parent / "channels.yaml"
    if not channels_file.exists():
        print(f"⚠️  警告: 找不到 channels.yaml 文件: {channels_file}")
        return []
    
    try:
        with open(channels_file, 'r', encoding='utf-8') as f:
            data = yaml.safe_load(f)
            if data is None:
                print(f"⚠️  警告: channels.yaml 文件为空或格式错误")
                return []
            channels = data.get('whitelist_channels', [])
            print(f"✅ 加载了 {len(channels)} 个白名单频道")
            return channels
    except Exception as e:
        print(f"⚠️  读取 channels.yaml 失败: {e}")
        return []


def search_youtube_videos(query: str, max_results: int = 10, api_key: str = None):
    """搜索 YouTube 视频"""
    if not api_key:
        api_key = load_youtube_api_key()
    
    if not api_key:
        print("❌ 错误: 未找到 YouTube API Key")
        print("💡 请设置环境变量 YOUTUBE_API_KEY 或在 config 文件中配置")
        return None
    
    try:
        url = "https://www.googleapis.com/youtube/v3/search"
        params = {
            "key": api_key,
            "q": query,
            "part": "snippet",
            "type": "video",
            "maxResults": min(max_results, 50),  # API limit
            "order": "relevance"
        }
        
        response = httpx.get(url, params=params, timeout=10.0)
        response.raise_for_status()
        
        data = response.json()
        
        if "items" not in data or not data["items"]:
            return []
        
        videos = []
        for item in data["items"]:
            video_info = {
                "video_id": item["id"]["videoId"],
                "title": item["snippet"]["title"],
                "description": item["snippet"]["description"],
                "channel_title": item["snippet"]["channelTitle"],
                "channel_id": item["snippet"]["channelId"],
                "published_at": item["snippet"]["publishedAt"],
                "thumbnail": item["snippet"]["thumbnails"].get("medium", {}).get("url", ""),
                "url": f"https://www.youtube.com/watch?v={item['id']['videoId']}",
                "query": query  # 记录搜索关键词
            }
            videos.append(video_info)
        
        return videos
    
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 403:
            print(f"❌ 错误: API 密钥无效或配额已用完 (查询: {query})")
        else:
            print(f"❌ HTTP 错误: {e.response.status_code} (查询: {query})")
        return None
    except Exception as e:
        print(f"❌ 搜索失败 (查询: {query}): {str(e)}")
        return None


def parse_published_time(published_at_str: str):
    """解析发布时间字符串为 datetime 对象"""
    try:
        # YouTube API 返回 ISO 8601 格式: 2024-01-01T12:00:00Z
        dt = datetime.fromisoformat(published_at_str.replace('Z', '+00:00'))
        return dt
    except Exception as e:
        print(f"⚠️  解析发布时间失败: {published_at_str}, 错误: {e}")
        return None


def is_within_time_window(published_at_str: str, days_window: int = DAYS_WINDOW):
    """检查视频是否在时间窗口内（默认14天）"""
    published_time = parse_published_time(published_at_str)
    if not published_time:
        return False
    
    now = datetime.now(timezone.utc)
    time_diff = now - published_time
    
    return time_diff <= timedelta(days=days_window)


def calculate_time_score(published_at_str: str):
    """计算时间评分：24小时内 +3，48小时内 +2"""
    published_time = parse_published_time(published_at_str)
    if not published_time:
        return 0
    
    now = datetime.now(timezone.utc)
    time_diff = now - published_time
    
    if time_diff <= timedelta(hours=24):
        return 3
    elif time_diff <= timedelta(hours=48):
        return 2
    else:
        return 0


def count_theme_keywords(text: str, themes: list):
    """计算文本中命中的主题关键词数量（不区分大小写）"""
    if not text:
        return 0
    
    text_lower = text.lower()
    count = 0
    for theme in themes:
        if theme.lower() in text_lower:
            count += 1
    return count


def score_video(video: dict, themes: list, whitelist_channels: list):
    """为视频计算评分"""
    score = 0
    
    # 1. 白名单频道评分 +10
    if video['channel_title'] in whitelist_channels:
        score += 10
    
    # 2. 标题或描述中每命中1个主题关键词 +5
    title_matches = count_theme_keywords(video['title'], themes)
    desc_matches = count_theme_keywords(video['description'], themes)
    keyword_score = (title_matches + desc_matches) * 5
    score += keyword_score
    
    # 3. 发布时间评分
    time_score = calculate_time_score(video['published_at'])
    score += time_score
    
    return score


def merge_and_deduplicate_videos(all_videos: list):
    """合并视频列表并按 videoId 去重"""
    video_dict = {}
    
    for video in all_videos:
        video_id = video['video_id']
        if video_id not in video_dict:
            video_dict[video_id] = video
        else:
            # 如果已存在，合并查询关键词
            existing_queries = video_dict[video_id].get('queries', [])
            if isinstance(existing_queries, str):
                existing_queries = [existing_queries]
            if video['query'] not in existing_queries:
                existing_queries.append(video['query'])
            video_dict[video_id]['queries'] = existing_queries
    
    return list(video_dict.values())


def generate_action(videos: list):
    """生成 action 字段：从 Top1 生成1条可执行动作（≤15min）"""
    if not videos:
        return "暂无推荐视频"
    
    # 只使用 Top1
    top1 = videos[0]
    action = f"观看《{top1['title']}》({top1['channel_title']})，预计≤15分钟"
    
    return action


def has_clickbait_words(title: str):
    """检查标题中是否包含标题党词汇"""
    clickbait_words = ['INSANE', 'HYPE', 'SHOCKING', 'UNBELIEVABLE', 'MIND-BLOWING', 
                       'AMAZING', 'INCREDIBLE', 'YOU WON\'T BELIEVE', 'THIS WILL BLOW YOUR MIND']
    title_upper = title.upper()
    for word in clickbait_words:
        if word in title_upper:
            return True
    return False


def is_older_than_days(published_at_str: str, days: int = 30):
    """检查视频是否超过指定天数"""
    published_time = parse_published_time(published_at_str)
    if not published_time:
        return False
    
    now = datetime.now(timezone.utc)
    time_diff = now - published_time
    
    return time_diff > timedelta(days=days)


def generate_risk(videos: list, themes: list):
    """生成 risk 字段：偏差检测"""
    if not videos:
        return "无风险"
    
    # 只检查 Top3
    top3 = videos[:3]
    warnings = []
    
    # 检查是否有超过30天的视频
    old_videos = []
    for video in top3:
        if is_older_than_days(video['published_at'], days=30):
            old_videos.append(video['title'])
    
    if old_videos:
        warnings.append(f"Top3中存在超过30天的视频: {', '.join(old_videos[:2])}")
    
    # 检查是否有标题党词汇
    clickbait_videos = []
    for video in top3:
        if has_clickbait_words(video['title']):
            clickbait_videos.append(video['title'])
    
    if clickbait_videos:
        warnings.append(f"检测到标题党词汇: {', '.join(clickbait_videos[:2])}")
    
    # 如果有警告，返回警告；否则返回正面评价
    if warnings:
        return "; ".join(warnings)
    else:
        return "今日信号较新且较可信"


def init_research_llm():
    """初始化用于研究模式的 LLM（使用通义千问/ModelScope配置）"""
    if not LLM_AVAILABLE:
        print("⚠️  警告: hello_agents 模块未安装，无法使用研究模式")
        return None
    
    # 从环境变量读取 LLM 配置（优先级顺序，与 chapter9 保持一致）
    # 优先使用 ModelScope 配置（通义千问）
    llm_model = (
        os.getenv("LLM_MODEL") or 
        os.getenv("LLM_MODEL_ID") or
        "Qwen/Qwen2.5-7B-Instruct"  # 默认通义千问模型
    )
    llm_api_key = (
        os.getenv("LLM_API_KEY") or  # 优先使用 LLM_API_KEY（阿里云通义千问）
        os.getenv("MODELSCOPE_API_KEY") or 
        os.getenv("MODELSCOPE_API_TOKEN")
    )
    llm_base_url = (
        os.getenv("LLM_BASE_URL") or 
        "https://api-inference.modelscope.cn/v1/"  # ModelScope 默认地址
    )
    llm_provider = os.getenv("LLM_PROVIDER", "modelscope")
    
    if not llm_api_key:
        print("⚠️  警告: 未找到 LLM API Key，研究模式需要配置 LLM")
        print("💡 请设置环境变量（推荐在 .env 文件中配置）:")
        print("   MODELSCOPE_API_KEY=your-modelscope-token-here")
        print("   LLM_MODEL=Qwen/Qwen2.5-7B-Instruct")
        print("   LLM_BASE_URL=https://api-inference.modelscope.cn/v1/")
        print("   LLM_PROVIDER=modelscope")
        return None
    
    try:
        llm = HelloAgentsLLM(
            model=llm_model,
            api_key=llm_api_key,
            base_url=llm_base_url,
            provider=llm_provider
        )
        print(f"✅ LLM 初始化成功: {llm_model} ({llm_provider})")
        return llm
    except Exception as e:
        print(f"⚠️  初始化 LLM 失败: {e}")
        return None


def prepare_sources_data(top3_videos: list):
    """从 Top3 视频中提取 sources 数据"""
    sources = []
    for video in top3_videos:
        sources.append({
            "title": video['title'],
            "channel": video['channel_title'],
            "url": video['url'],
            "published_at": video['published_at'],
            "score": video['score']
        })
    return sources


def extract_json_from_text(text: str):
    """从文本中提取 JSON 内容（处理 LLM 可能返回的格式化文本）"""
    # 尝试直接解析
    try:
        return json.loads(text.strip())
    except json.JSONDecodeError:
        pass
    
    # 尝试提取 JSON 代码块
    json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
    if json_match:
        try:
            return json.loads(json_match.group(1))
        except json.JSONDecodeError:
            pass
    
    # 尝试提取第一个完整的 JSON 对象
    json_match = re.search(r'\{.*\}', text, re.DOTALL)
    if json_match:
        try:
            return json.loads(json_match.group(0))
        except json.JSONDecodeError:
            pass
    
    return None


def generate_research_report(top3_videos: list, themes: list, llm):
    """使用 LLM 生成研究报告"""
    if not top3_videos:
        return None
    
    # 构建视频信息文本
    videos_info = []
    for i, video in enumerate(top3_videos, 1):
        videos_info.append(
            f"{i}. 标题: {video['title']}\n"
            f"   频道: {video['channel_title']}\n"
            f"   发布时间: {video['published_at']}\n"
            f"   评分: {video['score']}分\n"
            f"   链接: {video['url']}"
        )
    
    videos_text = "\n\n".join(videos_info)
    themes_text = ", ".join(themes)
    
    # 构建 prompt
    prompt = f"""基于以下 Top3 YouTube 视频信息，生成一份结构化研究报告。

视频信息：
{videos_text}

搜索主题：{themes_text}

请以 JSON 格式返回以下内容：
1. question: 一个核心问题，概括这些视频的共同关注点
2. key_findings: 3条发现，每条1句话，基于标题/频道/发布时间推断，使用"可能/倾向"等措辞
3. why_it_matters_to_me: 为什么这些信息对我重要（个性化解释）
4. next_steps: 1-3条行动建议，每条≤15分钟

请严格按照以下 JSON 格式返回（不要包含其他文字）：
{{
  "question": "核心问题",
  "key_findings": [
    "发现1（使用可能/倾向等措辞）",
    "发现2（使用可能/倾向等措辞）",
    "发现3（使用可能/倾向等措辞）"
  ],
  "why_it_matters_to_me": "个性化解释",
  "next_steps": [
    "行动建议1（≤15分钟）",
    "行动建议2（≤15分钟）",
    "行动建议3（≤15分钟）"
  ]
}}"""

    messages = [
        {"role": "system", "content": "你是一位专业的研究分析师，擅长从视频信息中提取关键洞察并给出可执行的行动建议。请始终以 JSON 格式返回结果。"},
        {"role": "user", "content": prompt}
    ]
    
    try:
        print("\n🔬 正在使用 LLM 生成研究报告...")
        response = llm.invoke(messages)
        
        if not response:
            print("⚠️  LLM 返回空响应")
            return None
        
        # 提取 JSON
        research_data = extract_json_from_text(response)
        
        if not research_data:
            print(f"⚠️  无法解析 LLM 响应为 JSON，原始响应: {response[:200]}...")
            return None
        
        # 验证必需字段
        required_fields = ["question", "key_findings", "why_it_matters_to_me", "next_steps"]
        missing_fields = [field for field in required_fields if field not in research_data]
        if missing_fields:
            print(f"⚠️  LLM 响应缺少必需字段: {', '.join(missing_fields)}")
            return None
        
        # 确保 key_findings 是列表且有3条
        if not isinstance(research_data.get("key_findings"), list):
            research_data["key_findings"] = []
        if len(research_data["key_findings"]) != 3:
            # 如果不足3条，填充或截断
            while len(research_data["key_findings"]) < 3:
                research_data["key_findings"].append("暂无发现")
            research_data["key_findings"] = research_data["key_findings"][:3]
        
        # 确保 next_steps 是列表，最多3条
        if not isinstance(research_data.get("next_steps"), list):
            research_data["next_steps"] = []
        research_data["next_steps"] = research_data["next_steps"][:3]
        
        print("✅ 研究报告生成成功")
        return research_data
        
    except Exception as e:
        print(f"⚠️  生成研究报告时出错: {e}")
        import traceback
        traceback.print_exc()
        return None


def main():
    """主函数"""
    # 解析命令行参数
    parser = argparse.ArgumentParser(description="YouTube 视频搜索 - 多主题智能搜索与日报生成")
    parser.add_argument(
        "--mode",
        type=str,
        choices=["daily_signal", "research"],
        default="research",
        help="运行模式: research (默认，生成日报+研究报告) 或 daily_signal (仅生成日报)"
    )
    args = parser.parse_args()
    mode = args.mode
    
    print("=" * 70)
    print("YouTube 视频搜索 - 多主题智能搜索与日报生成")
    if mode == "research":
        print("运行模式: 研究模式 (将生成日报 + 研究报告)")
    else:
        print("运行模式: 日报模式 (仅生成日报)")
    print("=" * 70)
    
    # 1. 加载配置
    themes = load_themes()
    if not themes:
        print("❌ 无法加载主题列表，退出")
        return
    
    whitelist_channels = load_whitelist_channels()
    api_key = load_youtube_api_key()
    if not api_key:
        print("❌ 无法加载 API Key，退出")
        return
    
    # 2. 对每个主题搜索
    print(f"\n🔍 开始搜索 {len(themes)} 个主题...")
    all_videos = []
    
    for theme in themes:
        print(f"  搜索主题: {theme}")
        videos = search_youtube_videos(theme, max_results=10, api_key=api_key)
        if videos:
            all_videos.extend(videos)
            print(f"    ✅ 找到 {len(videos)} 个视频")
        else:
            print(f"    ⚠️  未找到视频或搜索失败")
    
    if not all_videos:
        print("❌ 未找到任何视频，退出")
        return
    
    print(f"\n📊 合并前共找到 {len(all_videos)} 个视频")
    
    # 3. 合并去重
    unique_videos = merge_and_deduplicate_videos(all_videos)
    print(f"📊 去重后剩余 {len(unique_videos)} 个唯一视频")
    
    # 4. 时间窗口过滤：只考虑最近 DAYS_WINDOW 天的视频
    print(f"\n⏰ 应用时间窗口过滤（{DAYS_WINDOW}天）...")
    filtered_videos = [v for v in unique_videos if is_within_time_window(v['published_at'], DAYS_WINDOW)]
    excluded_count = len(unique_videos) - len(filtered_videos)
    if excluded_count > 0:
        print(f"   ⚠️  过滤掉 {excluded_count} 个超过 {DAYS_WINDOW} 天的视频")
    print(f"   ✅ 剩余 {len(filtered_videos)} 个视频参与排序")
    
    if not filtered_videos:
        print(f"❌ 时间窗口内（{DAYS_WINDOW}天）未找到任何视频，退出")
        return
    
    # 5. 评分
    print(f"\n⭐ 开始评分...")
    for video in filtered_videos:
        score = score_video(video, themes, whitelist_channels)
        video['score'] = score
        video['scoring_details'] = {
            'whitelist_bonus': 10 if video['channel_title'] in whitelist_channels else 0,
            'keyword_matches': count_theme_keywords(video['title'], themes) + count_theme_keywords(video['description'], themes),
            'time_bonus': calculate_time_score(video['published_at'])
        }
    
    # 6. 排序并取 Top 3
    sorted_videos = sorted(filtered_videos, key=lambda x: x['score'], reverse=True)
    top3_videos = sorted_videos[:3]
    
    print(f"\n🏆 Top 3 视频:")
    for i, video in enumerate(top3_videos, 1):
        print(f"  {i}. [{video['score']}分] {video['title']}")
        print(f"     频道: {video['channel_title']}")
        print(f"     链接: {video['url']}")
    
    # 7. 生成日期字符串
    today = datetime.now().strftime("%Y-%m-%d")
    
    # 8. 创建输出目录
    base_dir = Path(__file__).parent
    raw_dir = base_dir / "raw" / "youtube"
    archive_dir = base_dir / "archive" / "youtube"
    raw_dir.mkdir(parents=True, exist_ok=True)
    archive_dir.mkdir(parents=True, exist_ok=True)
    
    # 9. 保存原始数据
    raw_file = raw_dir / f"{today}_raw.json"
    raw_data = {
        "date": today,
        "themes_used": themes,
        "whitelist_channels": whitelist_channels,
        "days_window": DAYS_WINDOW,
        "total_videos_found": len(all_videos),
        "unique_videos": len(unique_videos),
        "filtered_videos_count": len(filtered_videos),
        "all_videos": sorted_videos  # 保存过滤后的视频，按评分排序
    }
    
    try:
        with open(raw_file, 'w', encoding='utf-8') as f:
            json.dump(raw_data, f, indent=2, ensure_ascii=False)
        print(f"\n💾 原始数据已保存到: {raw_file}")
    except Exception as e:
        print(f"❌ 保存原始数据失败: {e}")
        return
    
    # 10. 生成并保存日报
    action = generate_action(top3_videos)
    risk = generate_risk(sorted_videos, themes)
    
    daily_report = {
        "date": today,
        "themes_used": themes,
        "dimensions": [],  # 新增：用户可选的维度标签（如：["健康", "情绪", "工作"]），向后兼容
        "top3": [
            {
                "title": video['title'],
                "channel": video['channel_title'],
                "url": video['url'],
                "score": video['score'],
                "published_at": video['published_at'],
                "scoring_details": video['scoring_details']
            }
            for video in top3_videos
        ],
        "action": action,
        "risk": risk
    }
    
    archive_file = archive_dir / f"{today}.json"
    try:
        with open(archive_file, 'w', encoding='utf-8') as f:
            json.dump(daily_report, f, indent=2, ensure_ascii=False)
        print(f"💾 日报信号已保存到: {archive_file}")
    except Exception as e:
        print(f"❌ 保存日报信号失败: {e}")
        return
    
    # 11. 如果模式是 research，生成研究报告
    if mode == "research":
        llm = init_research_llm()
        if llm:
            try:
                research_report = generate_research_report(top3_videos, themes, llm)
                if research_report:
                    # 添加 sources 字段
                    research_report["sources"] = prepare_sources_data(top3_videos)
                    research_report["date"] = today
                    research_report["themes_used"] = themes
                    
                    # 保存研究报告
                    research_file = archive_dir / f"{today}_research.json"
                    with open(research_file, 'w', encoding='utf-8') as f:
                        json.dump(research_report, f, indent=2, ensure_ascii=False)
                    print(f"\n💾 研究报告已保存到: {research_file}")
                    
                    # 显示研究报告摘要
                    print("\n" + "=" * 70)
                    print("🔬 研究报告摘要")
                    print("=" * 70)
                    print(f"核心问题: {research_report.get('question', 'N/A')}")
                    print(f"\n关键发现:")
                    for i, finding in enumerate(research_report.get('key_findings', []), 1):
                        print(f"  {i}. {finding}")
                    print(f"\n为什么重要: {research_report.get('why_it_matters_to_me', 'N/A')}")
                    print(f"\n下一步行动:")
                    for i, step in enumerate(research_report.get('next_steps', []), 1):
                        print(f"  {i}. {step}")
                    print("=" * 70)
                else:
                    print("⚠️  研究报告生成失败，已跳过")
            except Exception as e:
                print(f"⚠️  生成研究报告时出错: {e}")
                import traceback
                traceback.print_exc()
        else:
            print("⚠️  未配置 LLM，跳过研究模式")
    
    # 12. 显示日报摘要
    print("\n" + "=" * 70)
    print("📄 日报摘要")
    print("=" * 70)
    print(f"日期: {daily_report['date']}")
    print(f"主题: {', '.join(daily_report['themes_used'])}")
    print(f"\n推荐行动 (Action):")
    print(f"  {daily_report['action']}")
    print(f"\n风险评估 (Risk):")
    print(f"  {daily_report['risk']}")
    print("=" * 70)


if __name__ == "__main__":
    main()
