# coding:utf8
import time
import logging
import requests
import threading
from typing import List, Dict, Any, Optional
from threading import Lock, Thread, Event

from . import sina, tencent, dc, basequotation


class RoundRobinQuotation(basequotation.BaseQuotation):
    """Round-robin A股实时行情获取器
    轮流调用sina、qq(tencent)、dc数据接口，提供故障自动切换功能
    """
    
    def __init__(self, enable_health_check=True):
        """初始化 RoundRobin 行情获取器
        :param enable_health_check: 是否启用后台健康检查线程（默认True）
        """
        super().__init__()

        # 初始化数据源
        self._sources = {
            'sina': sina.Sina(),
            'tencent': tencent.Tencent(),
            'dc': dc.DC()
        }

        # 轮询状态
        self._source_names = list(self._sources.keys())
        self._current_index = 0
        self._lock = Lock()

        # 故障处理
        self._failed_sources = set()
        self._retry_interval = 300  # 失败数据源重试间隔（秒），增加到5分钟
        self._last_retry_time = {}
        self._failure_threshold = 3  # 连续失败次数阈值
        self._failure_counts = {name: 0 for name in self._source_names}  # 记录每个数据源的连续失败次数
        self._last_success_time = {name: time.time() for name in self._source_names}  # 记录最后成功时间

        # 性能统计
        self._source_stats = {name: {'success': 0, 'failure': 0, 'avg_response_time': 0}
                             for name in self._source_names}

        self._logger = logging.getLogger(__name__)

        # 健康检查配置
        self._enable_health_check = enable_health_check
        self._health_check_interval = 30  # 健康检查间隔（秒）
        self._probe_timeout = 5  # 探测请求超时时间（秒）
        self._probe_stock_code = '000001'  # 用于探测的股票代码
        self._health_check_thread = None
        self._stop_event = Event()  # 用于优雅停止健康检查线程
        self._probe_clients = {}  # 独立探测客户端，避免与生产源共享状态
        self._probe_clients_lock = Lock()

        # 启动健康检查线程
        if self._enable_health_check:
            self._start_health_check_thread()

    @property
    def stock_api(self) -> str:
        """兼容基类接口，实际不使用"""
        return "round-robin"
    
    def _get_next_source(self) -> str:
        """获取下一个可用数据源 - 改进版本，更快的故障切换"""
        with self._lock:
            current_time = time.time()
            
            # 清理过期的失败源
            expired_sources = []
            for source in self._failed_sources:
                if (current_time - self._last_retry_time.get(source, 0)) > self._retry_interval:
                    expired_sources.append(source)
            
            for source in expired_sources:
                self._failed_sources.discard(source)
                self._failure_counts[source] = 0  # 重置失败计数
                self._logger.info(f"数据源 {source} 重试时间到，重新加入轮询")
            
            # 获取所有可用数据源（未被标记为失败的）
            available_sources = [s for s in self._source_names if s not in self._failed_sources]
            
            if not available_sources:
                # 如果所有数据源都失败了，强制重试最近成功过的数据源
                # 选择最近成功时间最晚的数据源
                best_source = max(self._source_names, 
                                key=lambda s: self._last_success_time.get(s, 0))
                self._logger.warning(f"所有数据源都不可用，强制重试最近成功的数据源: {best_source}")
                return best_source
            
            # 在可用数据源中进行round-robin选择
            # 更新索引以指向下一个可用数据源
            attempts = 0
            while attempts < len(self._source_names):
                candidate = self._source_names[self._current_index]
                self._current_index = (self._current_index + 1) % len(self._source_names)
                
                if candidate in available_sources:
                    return candidate
                    
                attempts += 1
            
            # 备用选择：返回第一个可用数据源
            return available_sources[0]
    
    def _mark_source_failed(self, source_name: str, is_timeout: bool = False):
        """标记数据源失败 - 改进版本，基于连续失败次数判断
        :param source_name: 数据源名称
        :param is_timeout: 是否为超时错误（超时错误会立即标记为不可用）
        """
        with self._lock:
            self._failure_counts[source_name] += 1
            self._source_stats[source_name]['failure'] += 1

            # 超时错误立即标记为不可用（无需等待连续失败）
            if is_timeout:
                self._failed_sources.add(source_name)
                self._last_retry_time[source_name] = time.time()
                self._logger.warning(f"数据源 {source_name} 超时，立即标记为不可用（将在{self._retry_interval}秒后重试）")
            # 连续失败次数达到阈值时，才将数据源标记为不可用
            elif self._failure_counts[source_name] >= self._failure_threshold:
                self._failed_sources.add(source_name)
                self._last_retry_time[source_name] = time.time()
                self._logger.warning(f"数据源 {source_name} 连续失败 {self._failure_counts[source_name]} 次，标记为不可用")
            else:
                self._logger.info(f"数据源 {source_name} 失败 ({self._failure_counts[source_name]}/{self._failure_threshold})")
    
    def _is_source_recently_failed(self, source_name: str) -> bool:
        """检查数据源是否最近失败过，用于快速跳过明显有问题的数据源"""
        current_time = time.time()
        last_success = self._last_success_time.get(source_name, 0)
        
        # 如果超过2分钟没有成功，且有失败记录，则认为最近失败过
        return (current_time - last_success > 120 and 
                self._failure_counts.get(source_name, 0) > 0)
    
    def _mark_source_success(self, source_name: str, response_time: float):
        """标记数据源成功 - 改进版本，重置失败状态"""
        with self._lock:
            # 成功时立即从失败列表中移除，并重置失败计数
            self._failed_sources.discard(source_name)
            self._failure_counts[source_name] = 0
            self._last_success_time[source_name] = time.time()
            
            stats = self._source_stats[source_name]
            stats['success'] += 1
            # 简单的移动平均
            stats['avg_response_time'] = (stats['avg_response_time'] + response_time) / 2
    
    def _normalize_data_format(self, data: Dict[str, Any], source_name: str, return_format: str = 'digit') -> Dict[str, Any]:
        """统一数据格式，将不同数据源的格式标准化为sina格式
        :param return_format: 如果是'prefix'格式，则保持键的格式不变
        """
        # 对于prefix格式，直接返回原始数据，不进行键转换
        if return_format == 'prefix':
            if source_name == 'sina':
                return data
            elif source_name == 'tencent':
                return self._convert_tencent_to_sina(data, preserve_keys=True)
            elif source_name == 'dc':
                return self._convert_dc_to_sina(data, preserve_keys=True)
            else:
                return data
        else:
            # 其他格式按原来的逻辑处理
            if source_name == 'sina':
                return data
            elif source_name == 'tencent':
                return self._convert_tencent_to_sina(data)
            elif source_name == 'dc':
                return self._convert_dc_to_sina(data)
            else:
                return data
    
    def _convert_tencent_to_sina(self, data: Dict[str, Any], preserve_keys: bool = False) -> Dict[str, Any]:
        """将腾讯数据格式转换为sina格式
        :param preserve_keys: 是否保持原始键格式（用于prefix格式）
        """
        normalized = {}
        for code, stock_data in data.items():
            try:
                # 处理时间格式
                datetime_obj = stock_data.get('datetime')
                if datetime_obj:
                    import datetime as dt
                    if hasattr(datetime_obj, 'strftime'):
                        # datetime对象
                        date = datetime_obj.strftime('%Y-%m-%d')
                        time_str = datetime_obj.strftime('%H:%M:%S')
                    else:
                        # struct_time对象
                        date = time.strftime('%Y-%m-%d', datetime_obj)
                        time_str = time.strftime('%H:%M:%S', datetime_obj)
                else:
                    date = time.strftime('%Y-%m-%d')
                    time_str = time.strftime('%H:%M:%S')
                
                normalized[code] = {
                    'name': stock_data.get('name', ''),
                    'open': stock_data.get('open', 0.0),
                    'close': stock_data.get('close', 0.0),
                    'now': stock_data.get('now', 0.0),
                    'high': stock_data.get('high', 0.0),
                    'low': stock_data.get('low', 0.0),
                    'buy': stock_data.get('bid1', 0.0),
                    'sell': stock_data.get('ask1', 0.0),
                    'turnover': int(stock_data.get('成交量(手)', 0)),
                    'volume': stock_data.get('成交额(万)', 0.0) / 10000,  # 转换为万元
                    'bid1_volume': int(stock_data.get('bid1_volume', 0)),
                    'bid1': stock_data.get('bid1', 0.0),
                    'bid2_volume': int(stock_data.get('bid2_volume', 0)),
                    'bid2': stock_data.get('bid2', 0.0),
                    'bid3_volume': int(stock_data.get('bid3_volume', 0)),
                    'bid3': stock_data.get('bid3', 0.0),
                    'bid4_volume': int(stock_data.get('bid4_volume', 0)),
                    'bid4': stock_data.get('bid4', 0.0),
                    'bid5_volume': int(stock_data.get('bid5_volume', 0)),
                    'bid5': stock_data.get('bid5', 0.0),
                    'ask1_volume': int(stock_data.get('ask1_volume', 0)),
                    'ask1': stock_data.get('ask1', 0.0),
                    'ask2_volume': int(stock_data.get('ask2_volume', 0)),
                    'ask2': stock_data.get('ask2', 0.0),
                    'ask3_volume': int(stock_data.get('ask3_volume', 0)),
                    'ask3': stock_data.get('ask3', 0.0),
                    'ask4_volume': int(stock_data.get('ask4_volume', 0)),
                    'ask4': stock_data.get('ask4', 0.0),
                    'ask5_volume': int(stock_data.get('ask5_volume', 0)),
                    'ask5': stock_data.get('ask5', 0.0),
                    'date': date,
                    'time': time_str,
                }
            except Exception as e:
                self._logger.warning(f"转换腾讯数据格式失败 {code}: {e}")
                continue
                
        return normalized
    
    def _convert_dc_to_sina(self, data: Dict[str, Any], preserve_keys: bool = False) -> Dict[str, Any]:
        """将东方财富数据格式转换为sina格式
        :param preserve_keys: 是否保持原始键格式（用于prefix格式）
        """
        # DC数据格式已经与sina基本一致，直接返回
        return data
    
    def real(self, stock_codes, prefix=False, max_retries=3, return_format=None):
        """获取指定股票的实时行情，支持Round-robin和故障切换 (增强版)
        :param stock_codes: 股票代码或股票代码列表，
                支持多种格式：数字格式(000001), 前缀格式(sz000001), 国标格式(000001.SZ)
        :param prefix: 是否在返回键中包含市场前缀（当return_format='national'时此参数被忽略）
        :param max_retries: 最大重试次数
        :param return_format: 返回数据中股票代码的格式 ('digit': 000001, 'prefix': sz000001, 'national': 000001.SZ)
                    如果为None，使用全局配置的默认格式
        :return: 行情字典
        """
        # 导入配置模块
        from . import config
        
        # 如果没有指定return_format，使用全局配置
        if return_format is None:
            return_format = config.get_config().default_return_format
        
        if not isinstance(stock_codes, list):
            stock_codes = [stock_codes]
        
        # 预处理：验证和标准化股票代码
        from . import helpers
        valid_codes = []
        for code in stock_codes:
            if helpers.validate_stock_code(code):
                valid_codes.append(code)
            else:
                self._logger.warning(f"跳过无效股票代码 {code}。{helpers.format_stock_code_examples()}")
        
        if not valid_codes:
            self._logger.error("没有有效的股票代码")
            return {}
        
        # 改进的重试逻辑：尝试所有可用数据源
        attempted_sources = set()
        total_attempts = 0
        
        while total_attempts < max_retries:
            # 获取下一个数据源
            source_name = self._get_next_source()
            
            # 如果已经尝试过所有数据源但仍未成功，等待一小段时间后重置尝试记录
            if source_name in attempted_sources and len(attempted_sources) >= len(self._source_names):
                self._logger.info(f"已尝试所有数据源，等待1秒后重置尝试记录")
                time.sleep(1)  # 短暂等待
                attempted_sources.clear()
            
            attempted_sources.add(source_name)
            source = self._sources[source_name]
            
            # 跳过最近明显有问题的数据源（除非是最后尝试）
            if self._is_source_recently_failed(source_name) and total_attempts < max_retries - 1:
                self._logger.debug(f"跳过最近失败过的数据源 {source_name}")
                total_attempts += 1
                continue
            
            start_time = time.time()
            try:
                #self._logger.info(f"使用数据源 {source_name} 获取行情数据 (尝试 {total_attempts + 1}/{max_retries})")
                
                # 根据return_format调用底层接口
                if return_format == 'national':
                    # 国标格式时先获取数字格式数据，然后转换
                    raw_data = source.real(valid_codes, prefix=False, return_format='digit')
                elif return_format == 'prefix':
                    # 前缀格式
                    raw_data = source.real(valid_codes, prefix=True, return_format='prefix')  
                else:
                    # 数字格式或其他
                    raw_data = source.real(valid_codes, prefix=prefix, return_format=return_format)
                
                # 验证返回数据的有效性
                if raw_data and isinstance(raw_data, dict) and len(raw_data) > 0:
                    # 检查数据是否为空或无效
                    valid_data = {k: v for k, v in raw_data.items() 
                                if v and isinstance(v, dict) and v.get('now', 0) > 0}
                    
                    if valid_data:
                        response_time = time.time() - start_time
                        self._mark_source_success(source_name, response_time)
                        
                        # 数据格式标准化
                        normalized_data = self._normalize_data_format(valid_data, source_name, return_format)
                        
                        # 根据return_format进行最终格式转换
                        if return_format == 'national':
                            # 转换为国标格式，传入原始代码以保留市场信息
                            final_data = helpers.convert_data_keys_to_national_format(normalized_data, valid_codes)
                        elif return_format == 'prefix':
                            # 前缀格式已经在调用底层接口时设置了prefix=True，保持原有数据
                            final_data = normalized_data
                        else:
                            # 数字格式，保持原有数据
                            final_data = normalized_data
                        
                        #self._logger.info(f"数据源 {source_name} 成功返回 {len(final_data)} 条有效数据")
                        return final_data
                    else:
                        raise Exception("返回数据为空或无效（价格为0）")
                else:
                    raise Exception("返回数据为空")
                    
            except Exception as e:
                response_time = time.time() - start_time
                error_msg = str(e)

                # 判断是否为超时错误
                is_timeout = isinstance(e, (requests.exceptions.Timeout,
                                           requests.exceptions.ConnectTimeout,
                                           requests.exceptions.ReadTimeout))

                if is_timeout:
                    self._logger.error(f"数据源 {source_name} 请求超时 (耗时 {response_time:.2f}s)，立即切换到下一个数据源")
                else:
                    self._logger.error(f"数据源 {source_name} 获取数据失败 (耗时 {response_time:.2f}s): {error_msg}")

                # 标记数据源失败，超时错误立即标记
                self._mark_source_failed(source_name, is_timeout=is_timeout)
                total_attempts += 1

                # 超时错误或快速失败（如连接拒绝），立即尝试下一个数据源
                if (is_timeout or response_time < 5.0) and total_attempts < max_retries:
                    self._logger.info(f"{'超时' if is_timeout else '快速失败'}，立即尝试下一个数据源")
                    continue

                if total_attempts >= max_retries:
                    self._logger.error(f"已达到最大重试次数 {max_retries}，无法获取股票数据")
                    break
        
        # 所有尝试都失败了，返回空字典
        self._logger.error(f"未获取到{','.join(valid_codes)}的行情数据")
        return {}
    
    def get_source_stats(self) -> Dict[str, Any]:
        """获取数据源统计信息 - 增强版本"""
        with self._lock:
            current_time = time.time()
            stats = {
                'sources': dict(self._source_stats),
                'failed_sources': list(self._failed_sources),
                'current_source': self._source_names[self._current_index],
                'total_sources': len(self._source_names),
                'failure_counts': dict(self._failure_counts),
                'last_success_time': {
                    name: {
                        'timestamp': self._last_success_time.get(name, 0),
                        'seconds_ago': current_time - self._last_success_time.get(name, 0)
                    } for name in self._source_names
                },
                'available_sources': [s for s in self._source_names if s not in self._failed_sources],
                'failure_threshold': self._failure_threshold,
                'retry_interval': self._retry_interval
            }
        return stats
    
    def reset_failed_sources(self):
        """重置所有失败的数据源状态 - 增强版本"""
        with self._lock:
            self._failed_sources.clear()
            self._last_retry_time.clear()
            self._failure_counts = {name: 0 for name in self._source_names}
            self._logger.info("已重置所有失败数据源状态")
    
    def force_exclude_source(self, source_name: str, duration: int = 600):
        """强制排除指定数据源一段时间（用于维护等场景）
        :param source_name: 要排除的数据源名称
        :param duration: 排除持续时间（秒），默认10分钟
        """
        if source_name not in self._source_names:
            self._logger.warning(f"无效的数据源名称: {source_name}")
            return
            
        with self._lock:
            self._failed_sources.add(source_name)
            self._last_retry_time[source_name] = time.time()
            # 临时调整重试间隔
            original_interval = self._retry_interval
            self._retry_interval = duration
            self._logger.warning(f"强制排除数据源 {source_name} {duration} 秒")
            
        # 恢复原来的重试间隔（这里简化处理，实际可以用定时器）
        def restore_interval():
            time.sleep(5)  # 短暂延迟后恢复
            self._retry_interval = original_interval
        
        import threading
        threading.Thread(target=restore_interval, daemon=True).start()
    
    def get_health_summary(self) -> str:
        """获取数据源健康状况摘要"""
        stats = self.get_source_stats()
        available = stats['available_sources']
        failed = stats['failed_sources']

        summary = f"数据源状态: {len(available)}/{stats['total_sources']} 可用"
        if failed:
            summary += f", 故障源: {', '.join(failed)}"

        # 显示最近成功时间
        recent_failures = []
        for name, time_info in stats['last_success_time'].items():
            if time_info['seconds_ago'] > 300:  # 5分钟内没成功
                recent_failures.append(f"{name}({int(time_info['seconds_ago']/60)}分钟前)")

        if recent_failures:
            summary += f", 近期问题: {', '.join(recent_failures)}"

        return summary

    def _get_probe_client(self, source_name: str):
        """获取指定数据源的独立探测客户端"""
        source = self._sources.get(source_name)
        if not source:
            return None

        with self._probe_clients_lock:
            probe_client = self._probe_clients.get(source_name)

            # 如果不存在或类型变化（用户可能替换了数据源），重新创建
            if not probe_client or probe_client.__class__ is not source.__class__:
                try:
                    probe_client = source.__class__()
                except Exception as exc:
                    self._logger.warning(f"健康检查: 创建 {source_name} 探测客户端失败: {exc}")
                    return None
                self._probe_clients[source_name] = probe_client

        # 仅供健康检查线程使用，直接应用探测超时时间
        probe_client.timeout = self._probe_timeout
        return probe_client

    def _probe_source(self, source_name: str) -> bool:
        """探测单个数据源是否恢复正常
        :param source_name: 数据源名称
        :return: True表示数据源已恢复，False表示仍然失败
        """
        probe_client = self._get_probe_client(source_name)
        if not probe_client:
            return False

        try:
            # 使用单只股票进行轻量级探测
            start_time = time.time()
            data = probe_client.real(self._probe_stock_code, prefix=False, return_format='digit')
            response_time = time.time() - start_time

            # 验证数据有效性
            if data and isinstance(data, dict) and len(data) > 0:
                # 检查数据是否包含有效价格
                for stock_data in data.values():
                    if isinstance(stock_data, dict) and stock_data.get('now', 0) > 0:
                        self._logger.info(f"健康检查: 数据源 {source_name} 已恢复正常 (探测耗时 {response_time:.2f}s)")
                        return True

            self._logger.debug(f"健康检查: 数据源 {source_name} 返回数据无效")
            return False

        except requests.exceptions.Timeout:
            self._logger.debug(f"健康检查: 数据源 {source_name} 探测超时")
            return False
        except Exception as e:
            self._logger.debug(f"健康检查: 数据源 {source_name} 探测失败: {e}")
            return False

    def _health_check_loop(self):
        """后台健康检查循环（运行在独立线程中）"""
        self._logger.info(f"健康检查线程已启动，检查间隔: {self._health_check_interval}秒")

        while not self._stop_event.is_set():
            try:
                # 使用 wait() 代替 sleep()，支持可中断等待
                if self._stop_event.wait(timeout=self._health_check_interval):
                    # Event 被设置，退出循环
                    break

                # 获取当前失败的数据源列表（需要加锁）
                with self._lock:
                    failed_sources_copy = list(self._failed_sources)

                # 如果没有失败的数据源，跳过本次检查
                if not failed_sources_copy:
                    continue

                self._logger.debug(f"健康检查: 开始检查 {len(failed_sources_copy)} 个失败数据源")

                # 遍历所有失败的数据源并进行探测
                for source_name in failed_sources_copy:
                    # 检查是否需要停止
                    if self._stop_event.is_set():
                        break

                    # 探测数据源
                    if self._probe_source(source_name):
                        # 探测成功，恢复数据源（需要加锁）
                        with self._lock:
                            self._failed_sources.discard(source_name)
                            self._failure_counts[source_name] = 0
                            self._last_success_time[source_name] = time.time()
                            self._logger.info(f"数据源 {source_name} 已自动恢复并重新加入轮询")

            except Exception as e:
                self._logger.error(f"健康检查线程发生错误: {e}", exc_info=True)
                # 发生错误时短暂休眠，避免快速循环
                self._stop_event.wait(timeout=5)

        self._logger.info("健康检查线程已停止")

    def _start_health_check_thread(self):
        """启动健康检查线程"""
        if self._health_check_thread and self._health_check_thread.is_alive():
            self._logger.warning("健康检查线程已在运行")
            return

        self._stop_event.clear()
        self._health_check_thread = Thread(
            target=self._health_check_loop,
            name="HealthCheckThread",
            daemon=True  # 守护线程，主程序退出时自动结束
        )
        self._health_check_thread.start()
        self._logger.info("健康检查线程已启动")

    def stop_health_check(self, wait_timeout=5):
        """停止健康检查线程
        :param wait_timeout: 等待线程结束的超时时间（秒），默认5秒
        """
        if not self._health_check_thread or not self._health_check_thread.is_alive():
            self._logger.info("健康检查线程未运行")
            return

        self._logger.info("正在停止健康检查线程...")
        self._stop_event.set()  # 设置停止事件

        # 等待线程结束
        self._health_check_thread.join(timeout=wait_timeout)

        if self._health_check_thread.is_alive():
            self._logger.warning(f"健康检查线程在{wait_timeout}秒内未能停止")
        else:
            self._logger.info("健康检查线程已成功停止")

    def restart_health_check(self):
        """重启健康检查线程"""
        self.stop_health_check()
        self._start_health_check_thread()
    
    # 兼容基类接口
    def get_stocks_by_range(self, params):
        """兼容基类接口，但不直接使用"""
        pass
    
    def format_response_data(self, rep_data, **kwargs):
        """兼容基类接口，但实际格式化在_normalize_data_format中处理"""
        return rep_data
