#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
XHS Crawler Module
小红书爬虫模块 - 重构版本
"""

import logging
import uuid
import asyncio
from typing import Dict, Any, List, Optional, Tuple
from pathlib import Path
from pydantic import BaseModel, Field

# 静态文件路径配置
class StaticFilesConfig:
    """静态文件路径配置"""
    
    def __init__(self, base_path: str = None):
        if base_path:
            self.static_dir = Path(base_path)
        else:
            # 尝试多个可能的静态文件路径
            possible_paths = [
                Path(__file__).parent.parent.parent.parent / "static",  # 项目根目录/static
                Path(__file__).parent.parent.parent / "static",  # travel-algorithms/static
                Path("static"),  # 当前目录/static
                Path("./static"),  # 相对路径
                Path("../static"),  # 上级目录/static
            ]
            
            self.static_dir = None
            for path in possible_paths:
                if path.exists() and any(path.glob("*.js")):
                    self.static_dir = path
                    break
            
            if not self.static_dir:
                # 如果都找不到，创建一个空目录作为占位符
                self.static_dir = Path("static")
                self.static_dir.mkdir(exist_ok=True)
                logging.warning("未找到静态文件目录，请确保JavaScript文件在正确位置")
    
    def get_js_file_path(self, filename: str) -> Path:
        """获取JavaScript文件路径"""
        return self.static_dir / filename


class XHSNote(BaseModel):
    """小红书笔记数据模型"""
    note_id: str = Field(..., description="笔记ID")
    title: str = Field(..., description="笔记标题")
    content: str = Field(..., description="笔记内容")
    author: str = Field(..., description="作者名称")
    author_id: str = Field(..., description="作者ID")
    likes: int = Field(default=0, description="点赞数")
    comments: int = Field(default=0, description="评论数")
    shares: int = Field(default=0, description="分享数")
    collections: int = Field(default=0, description="收藏数")
    tags: List[str] = Field(default_factory=list, description="标签列表")
    images: List[str] = Field(default_factory=list, description="图片URL列表")
    video_url: Optional[str] = Field(default=None, description="视频URL")
    publish_time: Optional[str] = Field(default=None, description="发布时间")
    note_url: str = Field(..., description="笔记链接")


class XHSSearchResult(BaseModel):
    """小红书搜索结果"""
    keyword: str = Field(..., description="搜索关键词")
    notes: List[XHSNote] = Field(default_factory=list, description="笔记列表")
    total_count: int = Field(default=0, description="总数量")
    search_time: str = Field(..., description="搜索时间")
    sort_type: int = Field(default=0, description="排序类型")
    note_type: int = Field(default=0, description="笔记类型")


class SearchConfig(BaseModel):
    """搜索配置"""
    sort_type: int = Field(default=0, ge=0, le=4, description="排序类型")
    note_type: int = Field(default=0, ge=0, le=2, description="笔记类型")
    note_time: int = Field(default=0, ge=0, le=3, description="时间范围")
    note_range: int = Field(default=0, ge=0, le=3, description="查看范围")
    pos_distance: int = Field(default=0, ge=0, le=2, description="位置距离")
    geo: Optional[Dict[str, float]] = Field(default=None, description="地理位置")


class XHSCrawler:
    """小红书爬虫"""
    
    def __init__(self, config: Any):
        """
        初始化爬虫
        
        Args:
            config: 爬虫配置 (WebCrawlingConfig)
        """
        self.config = config
        self.logger = logging.getLogger(__name__)
        
        # 静态文件配置
        self.static_config = StaticFilesConfig()
        
        # 初始化统计信息
        self.stats = {
            "total_requests": 0,
            "successful_requests": 0,
            "failed_requests": 0,
            "total_notes_crawled": 0,
            "start_time": None,
            "last_request_time": None
        }
        
        # Cookie管理
        self.cookies = {}
        self.current_cookie = None
        
        # 检查依赖
        self._check_dependencies()
    
    def _check_dependencies(self):
        """检查爬虫依赖"""
        try:
            # 检查JavaScript文件
            required_js_files = [
                "xhs_xs_xsc_56.js",
                "xhs_xray.js", 
                "xhs_creator_xs.js"
            ]
            
            missing_files = []
            for js_file in required_js_files:
                js_path = self.static_config.get_js_file_path(js_file)
                if not js_path.exists():
                    missing_files.append(js_file)
            
            if missing_files:
                self.logger.warning(f"缺少JavaScript文件: {missing_files}")
                self.logger.warning(f"静态文件目录: {self.static_config.static_dir}")
                self.logger.warning("这可能影响爬虫功能，请确保文件位于正确位置")
            
            # 检查其他依赖
            try:
                import execjs
                self.logger.info("execjs 依赖检查通过")
            except ImportError:
                self.logger.error("缺少 execjs 依赖，请安装: pip install PyExecJS")
            
            try:
                import requests
                self.logger.info("requests 依赖检查通过")
            except ImportError:
                self.logger.error("缺少 requests 依赖，请安装: pip install requests")
                
        except Exception as e:
            self.logger.error(f"依赖检查失败: {e}")
    
    def set_cookies(self, cookie_string: str, name: str = "default"):
        """设置Cookie"""
        try:
            # 解析Cookie字符串
            cookies_dict = {}
            for item in cookie_string.split(';'):
                if '=' in item:
                    key, value = item.strip().split('=', 1)
                    cookies_dict[key] = value
            
            self.cookies[name] = {
                "cookie_string": cookie_string,
                "cookies_dict": cookies_dict,
                "created_time": str(uuid.uuid4()),  # 模拟时间戳
                "use_count": 0
            }
            
            self.current_cookie = name
            self.logger.info(f"Cookie设置成功: {name}")
            
        except Exception as e:
            self.logger.error(f"Cookie设置失败: {e}")
            raise ValueError(f"无效的Cookie格式: {e}")
    
    async def search_notes(
        self,
        keyword: str,
        max_notes: int = 20,
        sort_type: int = 0,
        note_type: int = 0,
        **kwargs
    ) -> Tuple[str, Dict[str, Any]]:
        """
        搜索小红书笔记
        
        Args:
            keyword: 搜索关键词
            max_notes: 最大笔记数量
            sort_type: 排序类型
            note_type: 笔记类型
            
        Returns:
            (请求ID, 搜索结果)
        """
        request_id = str(uuid.uuid4())
        
        try:
            self.logger.info(f"开始搜索小红书笔记: {keyword}")
            
            # 更新统计
            self.stats["total_requests"] += 1
            self.stats["last_request_time"] = str(uuid.uuid4())  # 模拟时间戳
            
            # 检查Cookie
            if not self.current_cookie:
                self.logger.warning("未设置Cookie，将使用模拟数据")
                return request_id, self._get_mock_search_result(keyword, max_notes)
            
            # 模拟网络请求延迟
            await asyncio.sleep(self.config.request_interval)
            
            # 这里应该调用实际的爬虫逻辑
            # 由于缺少JavaScript文件，暂时返回模拟数据
            search_result = self._get_mock_search_result(keyword, max_notes)
            
            self.stats["successful_requests"] += 1
            self.stats["total_notes_crawled"] += len(search_result.get("notes", []))
            
            self.logger.info(f"搜索完成: {keyword}, 找到 {len(search_result.get('notes', []))} 条笔记")
            
            return request_id, search_result
            
        except Exception as e:
            self.stats["failed_requests"] += 1
            self.logger.error(f"搜索失败: {e}")
            raise
    
    def _get_mock_search_result(self, keyword: str, max_notes: int) -> Dict[str, Any]:
        """获取模拟搜索结果"""
        import random
        
        mock_notes = []
        for i in range(min(max_notes, 5)):  # 最多返回5条模拟数据
            note = {
                "note_id": f"mock_{keyword}_{i}",
                "title": f"{keyword}相关笔记 #{i+1}",
                "content": f"这是关于{keyword}的详细内容，包含了很多有用的信息和建议。",
                "author": f"用户_{i+1}",
                "author_id": f"user_{i+1}",
                "likes": random.randint(10, 1000),
                "comments": random.randint(5, 100),
                "shares": random.randint(1, 50),
                "collections": random.randint(5, 200),
                "tags": [keyword, "攻略", "推荐"],
                "images": [f"https://example.com/image_{i}.jpg"],
                "video_url": None if i % 2 == 0 else f"https://example.com/video_{i}.mp4",
                "publish_time": f"2024-01-{i+1:02d}",
                "note_url": f"https://www.xiaohongshu.com/discovery/item/{keyword}_{i}"
            }
            mock_notes.append(note)
        
        return {
            "keyword": keyword,
            "notes": mock_notes,
            "total_count": len(mock_notes),
            "search_time": str(uuid.uuid4()),  # 模拟时间戳
            "sort_type": 0,
            "note_type": 0,
            "status": "mock_data",
            "message": "使用模拟数据，请配置Cookie和静态文件以获取真实数据"
        }
    
    async def search_notes_batch(
        self,
        keywords: List[str],
        max_notes_per_keyword: int = 10,
        **kwargs
    ) -> Tuple[str, Dict[str, Any]]:
        """
        批量搜索小红书笔记
        
        Args:
            keywords: 搜索关键词列表
            max_notes_per_keyword: 每个关键词的最大笔记数量
            
        Returns:
            (请求ID, 批量搜索结果)
        """
        request_id = str(uuid.uuid4())
        
        try:
            self.logger.info(f"开始批量搜索: {len(keywords)} 个关键词")
            
            results = {}
            total_notes = 0
            
            for keyword in keywords:
                try:
                    _, search_result = await self.search_notes(
                        keyword=keyword,
                        max_notes=max_notes_per_keyword,
                        **kwargs
                    )
                    results[keyword] = search_result
                    total_notes += len(search_result.get("notes", []))
                    
                    # 请求间隔
                    await asyncio.sleep(self.config.request_interval)
                    
                except Exception as e:
                    self.logger.error(f"搜索关键词 '{keyword}' 失败: {e}")
                    results[keyword] = {
                        "error": str(e),
                        "notes": [],
                        "total_count": 0
                    }
            
            batch_result = {
                "keywords": keywords,
                "results": results,
                "total_keywords": len(keywords),
                "successful_keywords": len([k for k, v in results.items() if "error" not in v]),
                "total_notes": total_notes,
                "batch_time": str(uuid.uuid4())  # 模拟时间戳
            }
            
            self.logger.info(f"批量搜索完成: {len(keywords)} 个关键词, 共 {total_notes} 条笔记")
            
            return request_id, batch_result
            
        except Exception as e:
            self.logger.error(f"批量搜索失败: {e}")
            raise
    
    def get_crawler_stats(self) -> Dict[str, Any]:
        """获取爬虫统计信息"""
        return {
            **self.stats,
            "cookie_count": len(self.cookies),
            "current_cookie": self.current_cookie,
            "static_files_dir": str(self.static_config.static_dir),
            "config": {
                "request_interval": self.config.request_interval,
                "max_requests": self.config.max_requests,
                "request_timeout": self.config.request_timeout
            }
        } 