import os
import time
import random
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from playwright.sync_api import sync_playwright
from .cookie_manager import CookieManager
from .models import CrawlResult


class BaseSpider(ABC):
    """通用爬虫客户端抽象类"""
    
    def __init__(self):
        """
        初始化爬虫
        """
        self.cookie_manager = CookieManager()
    
    def get_spider_name(self) -> str:
        """
        获取爬虫名称
        
        Returns:
            爬虫名称
        """
        return self.__class__.__name__.lower().replace('spider', '')
    
    @abstractmethod
    def supports_url(self, url: str) -> bool:
        """
        校验链接
        
        Args:
            url: 要校验的URL
            
        Returns:
            是否支持该URL
        """
        pass
    
    @abstractmethod
    def requires_login(self) -> bool:
        """
        判断是否需要登录
        
        Returns:
            是否需要登录
        """
        pass
    
    def crawl(self, url: str) -> CrawlResult:
        """
        爬取链接（主方法）
        
        Args:
            url: 要爬取的URL
            
        Returns:
            爬取结果
        """
        # 生成资源唯一标识
        resource_id = self._generate_resource_id(url)
        
        try:
            print(f"使用playwright爬取内容...")
            return self._crawl_with_playwright(url, resource_id)
        except Exception as e:
            print(f"爬取内容失败: {e}")
            return CrawlResult(
                resource_id=resource_id,
                url=url,
                success=False,
                error=str(e)
            )
    
    def _generate_resource_id(self, url: str) -> str:
        """
        生成资源唯一标识
        
        Args:
            url: URL
            
        Returns:
            资源唯一标识
        """
        timestamp = int(time.time() * 1000)
        url_hash = hash(url) % 1000000
        return f"{self.get_spider_name()}_{url_hash}_{timestamp}"
    
    def _crawl_with_playwright(self, url: str, resource_id: str) -> CrawlResult:
        """
        使用playwright爬取内容
        
        Args:
            url: 要爬取的URL
            resource_id: 资源ID
            
        Returns:
            爬取结果
        """
        spider_name = self.get_spider_name()
        result = {
            'success': False,
            'content': '',
            'error': ''
        }
        
        # 判断是否需要登录
        requires_login = self.requires_login()
        
        # 判断cookie文件是否存在
        cookie_file_exists = self.cookie_manager.cookie_file_exists(spider_name)
        
        # 决定是否使用无头浏览器
        # 如果不需要登录，则使用无头浏览器
        # 如果需要登录，判断cookie文件是否存在
        #   - 如果cookie文件存在，使用无头浏览器
        #   - 如果cookie文件不存在，使用有头浏览器提醒用户登录
        headless_mode = True  # 默认使用无头浏览器
        
        if requires_login:
            if not cookie_file_exists:
                headless_mode = False  # 需要登录且没有cookie，使用有头浏览器
                print("需要登录且没有找到cookie文件，将打开浏览器让您手动登录...")
            else:
                print("需要登录且已找到cookie文件，使用无头浏览器...")
        else:
            print("不需要登录，使用无头浏览器...")
        
        try:
            with sync_playwright() as p:
                # 启动浏览器，根据登录状态决定是否显示窗口
                browser = p.chromium.launch(
                    headless=headless_mode,  # 根据登录状态决定是否显示浏览器窗口
                    args=[
                        '--no-sandbox',
                        '--disable-setuid-sandbox',
                        '--disable-dev-shm-usage',
                        '--disable-gpu',
                        '--disable-blink-features=AutomationControlled',  # 禁用自动化检测
                        '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
                    ],
                    slow_mo=50  # 增加操作延迟，模拟人类操作
                )
                
                # 创建上下文，设置额外的反爬参数
                context = browser.new_context(
                    user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
                    viewport={
                        'width': random.randint(1360, 1920),
                        'height': random.randint(768, 1080)
                    },
                    locale='zh-CN',
                    timezone_id='Asia/Shanghai',
                    geolocation={
                        'latitude': 39.9042,
                        'longitude': 116.4074
                    },
                    permissions=['geolocation']
                )
                
                # 创建页面
                page = context.new_page()
                
                # 加载保存的cookie
                saved_cookies = self.cookie_manager.load_cookies(spider_name)
                if saved_cookies:
                    print(f"加载了 {len(saved_cookies)} 个保存的cookie")
                    context.add_cookies(saved_cookies)
                elif requires_login and not cookie_file_exists:
                    # 只有在需要登录且没有cookie文件时才进行登录
                    print("需要登录且没有找到cookie文件，将打开浏览器让您手动登录...")
                    print("请在打开的浏览器中登录，登录成功后按回车键继续...")
                    
                    # 打开登录页面（这里需要子类实现，因为不同网站的登录页面不同）
                    login_url = self._get_login_url()
                    page.goto(login_url, timeout=60000)
                    
                    # 等待用户手动登录
                    input("请在浏览器中登录，登录成功后按回车键继续...")
                    
                    # 等待页面加载完成
                    page.wait_for_load_state('networkidle', timeout=60000)
                    
                    # 保存cookie
                    cookies = context.cookies()
                    if cookies:
                        self.cookie_manager.save_cookies(spider_name, cookies)
                        print(f"成功保存 {len(cookies)} 个cookie")
                else:
                    print("不需要登录或已有cookie文件，跳过登录步骤...")
                
                # 注入JavaScript，移除自动化检测
                page.add_init_script("""
                    Object.defineProperty(navigator, 'webdriver', {
                        get: () => undefined
                    });
                    Object.defineProperty(navigator, 'languages', {
                        get: () => ['zh-CN', 'zh', 'en-US', 'en']
                    });
                    Object.defineProperty(navigator, 'plugins', {
                        get: () => [1, 2, 3]
                    });
                    Object.defineProperty(navigator, 'mimeTypes', {
                        get: () => [1, 2, 3]
                    });
                    Object.defineProperty(document, 'currentScript', {
                        get: () => null
                    });
                    Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {
                        value: function() {
                            return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
                        }
                    });
                """)
                
                # 访问URL，增加随机延迟
                page.goto(url, timeout=60000)
                
                # 等待页面加载完成
                page.wait_for_load_state('networkidle', timeout=60000)
                
                # 随机延迟，模拟人类浏览
                time.sleep(random.uniform(1, 3))
                
                # 模拟页面滚动
                for i in range(3):
                    page.mouse.wheel(0, random.randint(200, 400))
                    time.sleep(random.uniform(0.5, 1.5))
                
                # 调用子类的浏览器动作方法，获取原始响应数据
                content = self.do_action(page)
                result['content'] = content
                
                # 保存cookie
                try:
                    cookies = context.cookies()
                    if cookies:
                        self.cookie_manager.save_cookies(spider_name, cookies)
                        print(f"成功保存 {len(cookies)} 个cookie")
                except Exception as e:
                    print(f"保存cookie失败: {e}")
                
                # 关闭上下文和浏览器
                context.close()
                browser.close()
                
                result['success'] = True
                
        except Exception as e:
            result['error'] = str(e)
            print(f"访问URL失败: {e}")
        
        if result['success']:
            # 构建爬取结果，只填入原始内容
            crawl_result = CrawlResult(
                resource_id=resource_id,
                url=url,
                success=True,
                content=result['content']
            )
            
            # 调用子类的内容处理方法，将处理结果放入content_parsed
            parsed_content = self.process_content(crawl_result.content)
            
            # 处理不同类型的返回结果
            if isinstance(parsed_content, dict):
                crawl_result.content_parsed = parsed_content
            elif hasattr(parsed_content, 'to_dict'):
                # 如果是自定义模型对象，转换为字典
                crawl_result.content_parsed = parsed_content.to_dict()
            else:
                # 如果返回的不是字典或模型对象，放入content_parsed的content字段
                crawl_result.content_parsed = {'content': parsed_content}
            
            return crawl_result
        else:
            # 返回错误结果
            return CrawlResult(
                resource_id=resource_id,
                url=url,
                success=False,
                error=result['error']
            )
    
    def _get_login_url(self) -> str:
        """
        获取登录页面URL
        
        Returns:
            登录页面URL
        """
        return 'https://www.google.com'
    
    @abstractmethod
    def do_action(self, page) -> str:
        """
        执行浏览器动作，获取原始响应数据
        
        Args:
            page: Playwright页面对象
            
        Returns:
            原始响应数据（HTML内容）
        """
        pass
    
    @abstractmethod
    def process_content(self, content: str) -> Any:
        """
        处理爬取到的内容
        
        Args:
            content: 原始内容
            
        Returns:
            处理后的内容
        """
        pass