#!/usr/bin/python
# -*- coding:UTF-8 -*-
"""
基于 Redis 的数据项去重管道
========================
提供分布式环境下的数据项去重功能，防止保存重复的数据记录。

特点:
- 分布式支持: 多节点共享去重数据
- 高性能: 使用 Redis 集合进行快速查找
- 可配置: 支持自定义 Redis 连接参数
- 容错设计: 网络异常时不会丢失数据
"""
import redis
import hashlib
from typing import Optional

from crawlo import Item
from crawlo.spider import Spider
from crawlo.exceptions import ItemDiscard
from crawlo.utils.fingerprint import FingerprintGenerator
from crawlo.logging import get_logger
from crawlo.utils.redis_manager import RedisKeyManager


class RedisDedupPipeline:
    """基于 Redis 的数据项去重管道"""

    def __init__(
            self,
            redis_host: str = 'localhost',
            redis_port: int = 6379,
            redis_db: int = 0,
            redis_password: Optional[str] = None,
            redis_key: str = 'crawlo:item_fingerprints'
    ):
        """
        初始化 Redis 去重管道
        
        :param redis_host: Redis 主机地址
        :param redis_port: Redis 端口
        :param redis_db: Redis 数据库编号
        :param redis_password: Redis 密码
        :param redis_key: 存储指纹的 Redis 键名
        """
        self.logger = get_logger(self.__class__.__name__)
        
        # 初始化 Redis 连接
        try:
            self.redis_client = redis.Redis(
                host=redis_host,
                port=redis_port,
                db=redis_db,
                password=redis_password,
                decode_responses=True,
                socket_connect_timeout=5,
                socket_timeout=5
            )
            # 测试连接
            self.redis_client.ping()
        except Exception as e:
            self.logger.error(f"Redis connection failed: {e}")
            raise RuntimeError(f"Redis 连接失败: {e}")

        self.redis_key = redis_key
        self.dropped_count = 0

    @classmethod
    def from_crawler(cls, crawler):
        """从爬虫配置创建管道实例"""
        settings = crawler.settings
        
        # 使用统一的Redis key命名规范
        key_manager = RedisKeyManager.from_settings(settings)
        # 如果有spider，更新key_manager中的spider_name
        if hasattr(crawler, 'spider') and crawler.spider:
            spider_name = getattr(crawler.spider, 'name', None)
            if spider_name:
                key_manager.set_spider_name(spider_name)
        redis_key = key_manager.get_item_fingerprint_key()
        
        return cls(
            redis_host=settings.get('REDIS_HOST', 'localhost'),
            redis_port=settings.get_int('REDIS_PORT', 6379),
            redis_db=settings.get_int('REDIS_DB', 0),
            redis_password=settings.get('REDIS_PASSWORD') or None,
            redis_key=redis_key
        )

    def process_item(self, item: Item, spider: Spider) -> Item:
        """
        处理数据项，进行去重检查
        
        :param item: 要处理的数据项
        :param spider: 爬虫实例
        :return: 处理后的数据项或抛出 ItemDiscard 异常
        """
        try:
            # 生成数据项指纹
            fingerprint = self._generate_item_fingerprint(item)
            
            # 使用 Redis 的 SADD 命令检查并添加指纹
            # 如果指纹已存在，SADD 返回 0；如果指纹是新的，SADD 返回 1
            is_new = self.redis_client.sadd(self.redis_key, fingerprint)
            
            if not is_new:
                # 如果指纹已存在，丢弃这个数据项
                self.dropped_count += 1
                self.logger.info(f"Dropping duplicate item: {fingerprint}")
                raise ItemDiscard(f"Duplicate item: {fingerprint}")
            else:
                # 如果是新数据项，继续处理
                self.logger.debug(f"Processing new item: {fingerprint}")
                return item
                
        except redis.RedisError as e:
            self.logger.error(f"Redis error: {e}")
            # 在 Redis 错误时继续处理，避免丢失数据
            return item
        except ItemDiscard:
            # 重新抛出ItemDiscard异常，确保管道管理器能正确处理
            raise
        except Exception as e:
            self.logger.error(f"Error processing item: {e}")
            # 在其他错误时继续处理
            return item

    def _generate_item_fingerprint(self, item: Item) -> str:
        """
        生成数据项指纹
        
        基于数据项的所有字段生成唯一指纹，用于去重判断。
        
        :param item: 数据项
        :return: 指纹字符串
        """
        return FingerprintGenerator.item_fingerprint(item)

    def close_spider(self, spider: Spider) -> None:
        """
        爬虫关闭时的清理工作
        
        :param spider: 爬虫实例
        """
        try:
            # 获取去重统计信息
            total_items = self.redis_client.scard(self.redis_key)
            self.logger.info(f"Spider {spider.name} closed:")
            self.logger.info(f"  - Dropped duplicate items: {self.dropped_count}")
            self.logger.info(f"  - Fingerprints stored in Redis: {total_items}")
            
            # 注意：默认情况下不清理 Redis 中的指纹
            # 如果需要清理，可以在设置中配置
            # 安全访问crawler和settings
            crawler = getattr(spider, 'crawler', None)
            if crawler and hasattr(crawler, 'settings'):
                settings = crawler.settings
                if settings.getbool('REDIS_DEDUP_CLEANUP', False):
                    deleted = self.redis_client.delete(self.redis_key)
                    self.logger.info(f"  - Cleaned fingerprints: {deleted}")
        except Exception as e:
            self.logger.error(f"Error closing spider: {e}")