import threading
import time
import random
import json
import datetime
from typing import List, Dict
from loguru import logger
from amazon_configs import site_name_secret_dict
from py_spider.utils.secure_db_client import get_remote_engine
def get_cookies_from_db(site_name: str) -> List[Dict]:
    """
    从数据库查询最近 days 天的 Cookie
    """
    if site_name not in site_name_secret_dict:
        logger.error(f"站点 {site_name} 不在配置列表中")
        return []

    try:
        # 1. 获取数据库连接
        engine = get_remote_engine(site_name=site_name, db_type="mysql")
        if not engine:
            logger.error(f"站点 {site_name} 数据库连接失败")
            return []
        """
        # 2. 计算时间节点 (当前时间 - 7天)
        # 格式化为 '2025-11-11 16:18:39'
        seven_days_ago_dt = datetime.datetime.now() - datetime.timedelta(days=days)
        seven_days_ago_str = seven_days_ago_dt.strftime("%Y-%m-%d %H:%M:%S")
        logger.info(f'7天前 时间为 {seven_days_ago_dt},{seven_days_ago_str}')
        # 3. 构建 SQL (注意：字段 updated_time 是 timestamp 类型)
        table_name = f"{site_name}_cookies"
        sql = f"SELECT * FROM {table_name} WHERE updated_time >= '{seven_days_ago_str}' ORDER BY updated_time DESC;"
        """
        sql = f"select * from {site_name}_cookies order by  updated_time desc limit 100;"
        logger.info(f"[{site_name}] 执行SQL查询最近 100条 Cookie: {sql.strip()}")
        df_cookies = engine.read_sql(sql)
        if df_cookies.empty:
            logger.warning(f"站点 {site_name}  无最近100条 Cookie数据")
            return []

        # 5. 解析数据
        valid_cookies = []
        for _, row in df_cookies.iterrows():
            try:
                cookie_str = row.get('cookies') # cookie数据
                updated_date = row.get('updated_time') # 更新时间
                # logger.info(f'更新时间为 {updated_date},{type(updated_date)}')
                if cookie_str:
                    if isinstance(cookie_str, str):
                        cookie_dict = json.loads(cookie_str)
                    else:
                        cookie_dict = cookie_str  # 已经是字典的情况
                    # 简单校验有效性
                    if "session-id" in cookie_dict:
                        if isinstance(updated_date, str):
                            clean_str = updated_date.replace('T', ' ')
                            updated_ts = datetime.datetime.strptime(clean_str, "%Y-%m-%d %H:%M:%S")
                            updated_ts = updated_ts.timestamp()
                        else:
                            updated_ts = time.time()  # 默认给当前时间（兜底）
                        # logger.info(f'更新时间 时间戳为{updated_ts}')

                        valid_cookies.append({
                            'cookie': cookie_dict,  # 给 requests 用的纯净字典
                            'updated_ts': updated_ts  # 判断过期用的时间戳
                        })
            except Exception as e:
                logger.warning(f"解析单条Cookie失败: {e}")
                continue

        return valid_cookies

    except Exception as e:
        logger.error(f"[{site_name}] 数据库读取异常: {e}")
        return []


# print(get_cookies_from_db('us'))
class CookiePoolManager:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                if not cls._instance:
                    cls._instance = super(CookiePoolManager, cls).__new__(cls)
                    cls._instance._init_pools()
        return cls._instance

    def _init_pools(self):
        """初始化池子状态"""
        # 结构: { "us": { "data": [{’cookie‘:{},updated_ts:''},{’cookie‘:{},updated_ts:''}], "initial_count": 100, "last_refresh": timestamp } }
        self.pools = {
            site: {"data": [], "initial_count": 0, "last_db_check": 0}
            for site in ["us", "uk", "de"]
        }
        self.cookie_expire_seconds = 1 * 24 * 60 * 60 # 数据更新时间：1天
        self.db_check_interval = 1* 60              # 查库冷却期：1分钟

    def _refresh_from_db(self, site_name: str):
        """内部方法：从数据库刷新"""
        new_data  = get_cookies_from_db(site_name) #返回的是列表 但是数据是字典格式 # [{’cookie‘:{},updated_ts:''},{’cookie‘:{},updated_ts:''}]
        if new_data:
            with self._lock:
                self.pools[site_name]["data"] = new_data
                self.pools[site_name]["initial_count"] = len(new_data)
                # 更新查库时间
                self.pools[site_name]["last_db_check"] = time.time()
            logger.success(f"[{site_name}] Cookie池已刷新，数量: {len(new_data)}")
        else:
            # 即使没查到数据，也更新查库时间，防止10分钟内反复无效查询
            self.pools[site_name]["last_db_check"] = time.time()
            logger.warning(f"[{site_name}] 数据库无有效数据")

    def get_cookie(self, site_name: str) -> Dict[str, str]:
        """
        对外核心接口：获取一个可用 Cookie
        包含：3天周期检查、半数阈值检查、空池初始化
        """
        site_pool = self.pools.get(site_name)
        if not site_pool:
            # 如果是不支持的站点，尝试初始化一下结构
            self.pools[site_name] = {"data": [], "initial_count": 0, "last_refresh": 0}
            site_pool = self.pools[site_name]

        # 1. 空池初始化
        if not site_pool["data"]:
            self._refresh_from_db(site_name)

        # === 策略2: 数量阈值刷新 (少于一半) ===
        # 只有在初始化成功过(initial_count > 0)的情况下才检查，防止数据库本来就没数据导致的死循环
        current_count = len(site_pool["data"])
        if site_pool["initial_count"] > 0 and current_count < (site_pool["initial_count"] / 2):
            # 也要受冷却时间限制，防止数据库一直没新数据导致死循环刷新
            if (time.time() - site_pool["last_db_check"]) > self.db_check_interval:
                logger.warning( f"[{site_name}] 可用Cookie({current_count}) 低于 初始({site_pool['initial_count']}) 一半，触发强制刷新...")
                self._refresh_from_db(site_name)

        # logger.info(f'cookie池内容 {site_pool["initial_count"]} {site_pool["last_refresh"]}')

        wrapper = {}
        # === 策略3: 随机获取 ===
        with self._lock:
            if site_pool["data"]:
                wrapper = random.choice(site_pool["data"])
        if not wrapper: return {}

        # 4. 【核心】基于单条数据的 updated_ts 判断过期
        current_ts = time.time()
        cookie_ts = wrapper.get('updated_ts', current_ts)
        if (current_ts - cookie_ts) > self.cookie_expire_seconds:
            last_check = site_pool["last_db_check"] # 拿到查库时间

            # 数据过期了，看看能不能查库
            if (current_ts - last_check) > self.db_check_interval:
                logger.info(f"[{site_name}] 抽到过期数据(>1天) {wrapper}，触发数据库更新...")
                self._refresh_from_db(site_name)

                # 刷新完再拿一次
                with self._lock:
                    if site_pool["data"]:
                        wrapper = random.choice(site_pool["data"])
            else:
                # 冷却期内，哪怕过期了也先用着 ，或者可以选择返回空 {} 让上层用兜底 冷却期就是 限制数据库只能10分钟查询一次
                logger.debug(f"[{site_name}] 数据过期但处于冷却期，降级使用")
        return  wrapper.get('cookie', {})

    def mark_invalid(self, site_name: str, bad_cookie: Dict):
        """
        对外接口：标记 Cookie 失效（从内存池中移除）
        """
        if not bad_cookie:
            return
        # logger.info(f'移除cookie {bad_cookie}')
        with self._lock:
            pool_data = self.pools.get(site_name, {}).get("data", []) # 获取cookie列表
            bad_sid = bad_cookie.get("session-id") # 获取过期cookie的session-id

            if bad_sid:
                original_len = len(pool_data)
                # 过滤掉这个 session-id
                self.pools[site_name]["data"] = [
                    c for c in pool_data if c.get('cookie').get("session-id") != bad_sid
                ]
                new_len = len(self.pools[site_name]["data"])
                # 判断删除cookie后的列表是否小于 原cookie 列表 确定移除是否成功
                if new_len < original_len:
                    logger.warning(f"[{site_name}] 移除失效Cookie (SID: {bad_sid}...), 剩余: {new_len}")


# 导出单例
cookie_manager = CookiePoolManager()