"""
智能盯盘 - K线图绘制模块
支持AI决策标注、实时更新
"""

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional
import logging


class SmartMonitorKline:
    """智能盯盘K线图"""
    
    def __init__(self):
        """初始化K线图"""
        self.logger = logging.getLogger(__name__)
    
    def create_kline_with_decisions(
        self,
        stock_code: str,
        stock_name: str,
        kline_data: pd.DataFrame,
        ai_decisions: List[Dict],
        show_volume: bool = True,
        show_ma: bool = True,
        height: int = 600
    ) -> go.Figure:
        """
        创建带AI决策标注的K线图
        
        Args:
            stock_code: 股票代码
            stock_name: 股票名称
            kline_data: K线数据（DataFrame）
            ai_decisions: AI决策列表
            show_volume: 是否显示成交量
            show_ma: 是否显示均线
            height: 图表高度
            
        Returns:
            plotly Figure对象
        """
        try:
            # 确保数据不为空
            if kline_data is None or kline_data.empty:
                self.logger.warning(f"K线数据为空 {stock_code}")
                return self._create_empty_figure(stock_code, stock_name, height)
            
            # 确保必需的列存在
            required_cols = ['日期', '开盘', '收盘', '最高', '最低']
            if not all(col in kline_data.columns for col in required_cols):
                self.logger.error(f"K线数据缺少必需列 {stock_code}")
                return self._create_empty_figure(stock_code, stock_name, height)
            
            # 创建子图
            if show_volume:
                fig = make_subplots(
                    rows=2, cols=1,
                    shared_xaxes=True,
                    vertical_spacing=0.03,
                    row_heights=[0.7, 0.3],
                    subplot_titles=(f'{stock_code} {stock_name}', '成交量')
                )
            else:
                fig = make_subplots(
                    rows=1, cols=1,
                    subplot_titles=(f'{stock_code} {stock_name}',)
                )
            
            # 1. 添加K线图
            fig.add_trace(
                go.Candlestick(
                    x=kline_data['日期'],
                    open=kline_data['开盘'],
                    high=kline_data['最高'],
                    low=kline_data['最低'],
                    close=kline_data['收盘'],
                    name='K线',
                    increasing_line_color='#ef5350',  # 红色（涨）
                    decreasing_line_color='#26a69a'   # 绿色（跌）
                ),
                row=1, col=1
            )
            
            # 2. 添加均线（如果需要）
            if show_ma:
                self._add_moving_averages(fig, kline_data, row=1, col=1)
            
            # 3. 添加AI决策标注
            if ai_decisions:
                self._add_ai_decision_markers(fig, kline_data, ai_decisions, row=1, col=1)
            
            # 4. 添加成交量（如果需要）
            if show_volume and '成交量' in kline_data.columns:
                self._add_volume(fig, kline_data, row=2, col=1)
            
            # 5. 更新布局
            fig.update_layout(
                height=height,
                xaxis_rangeslider_visible=False,
                showlegend=True,
                hovermode='x unified',
                template='plotly_white',
                margin=dict(l=50, r=50, t=50, b=50),
            )
            
            # 更新x轴
            fig.update_xaxes(
                title_text="日期",
                row=2 if show_volume else 1,
                col=1
            )
            
            # 更新y轴
            fig.update_yaxes(title_text="价格(元)", row=1, col=1)
            if show_volume:
                fig.update_yaxes(title_text="成交量", row=2, col=1)
            
            return fig
            
        except Exception as e:
            self.logger.error(f"创建K线图失败 {stock_code}: {e}")
            import traceback
            self.logger.debug(traceback.format_exc())
            return self._create_empty_figure(stock_code, stock_name, height)
    
    def _add_moving_averages(self, fig, kline_data: pd.DataFrame, row: int, col: int):
        """添加均线"""
        try:
            # 计算均线
            ma_periods = [5, 10, 20, 60]
            ma_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A']
            
            for period, color in zip(ma_periods, ma_colors):
                if len(kline_data) >= period:
                    ma = kline_data['收盘'].rolling(window=period).mean()
                    fig.add_trace(
                        go.Scatter(
                            x=kline_data['日期'],
                            y=ma,
                            name=f'MA{period}',
                            line=dict(color=color, width=1),
                            opacity=0.7
                        ),
                        row=row, col=col
                    )
        except Exception as e:
            self.logger.warning(f"添加均线失败: {e}")
    
    def _add_ai_decision_markers(
        self,
        fig,
        kline_data: pd.DataFrame,
        ai_decisions: List[Dict],
        row: int,
        col: int
    ):
        """在K线图上添加AI决策标注"""
        try:
            # 决策类型映射
            action_config = {
                'buy': {
                    'symbol': 'triangle-up',
                    'color': '#ef5350',
                    'text': '买入',
                    'size': 15
                },
                'sell': {
                    'symbol': 'triangle-down',
                    'color': '#26a69a',
                    'text': '卖出',
                    'size': 15
                },
                'add_position': {
                    'symbol': 'triangle-up',
                    'color': '#ff9800',
                    'text': '加仓',
                    'size': 12
                },
                'reduce_position': {
                    'symbol': 'triangle-down',
                    'color': '#9c27b0',
                    'text': '减仓',
                    'size': 12
                },
                'hold': {
                    'symbol': 'circle',
                    'color': '#607d8b',
                    'text': '持有',
                    'size': 8
                }
            }
            
            # 将K线数据日期转换为字符串，便于匹配
            kline_data['日期_str'] = pd.to_datetime(kline_data['日期']).dt.strftime('%Y-%m-%d')
            
            # 按决策类型分组
            for action_type, config in action_config.items():
                decisions_of_type = [d for d in ai_decisions if d.get('action') == action_type]
                
                if not decisions_of_type:
                    continue
                
                # 提取决策的日期和价格
                decision_dates = []
                decision_prices = []
                decision_texts = []
                
                for decision in decisions_of_type:
                    decision_date = decision.get('decision_time', '').split()[0]  # 只取日期部分
                    
                    # 在K线数据中查找对应日期的收盘价
                    matching_rows = kline_data[kline_data['日期_str'] == decision_date]
                    
                    if not matching_rows.empty:
                        price = matching_rows.iloc[0]['收盘']
                        decision_dates.append(decision_date)
                        decision_prices.append(price)
                        
                        # 构建hover文本
                        confidence = decision.get('confidence', 0)
                        reasoning = decision.get('reasoning', '无')[:50]  # 截断过长的推理
                        hover_text = (
                            f"<b>{config['text']}</b><br>"
                            f"日期: {decision_date}<br>"
                            f"价格: ¥{price:.2f}<br>"
                            f"置信度: {confidence}%<br>"
                            f"推理: {reasoning}..."
                        )
                        decision_texts.append(hover_text)
                
                # 添加标注
                if decision_dates:
                    fig.add_trace(
                        go.Scatter(
                            x=decision_dates,
                            y=decision_prices,
                            mode='markers+text',
                            name=config['text'],
                            marker=dict(
                                symbol=config['symbol'],
                                size=config['size'],
                                color=config['color'],
                                line=dict(color='white', width=1)
                            ),
                            text=[config['text']] * len(decision_dates),
                            textposition='top center',
                            textfont=dict(size=10, color=config['color']),
                            hovertext=decision_texts,
                            hoverinfo='text',
                            showlegend=True
                        ),
                        row=row, col=col
                    )
            
        except Exception as e:
            self.logger.error(f"添加AI决策标注失败: {e}")
            import traceback
            self.logger.debug(traceback.format_exc())
    
    def _add_volume(self, fig, kline_data: pd.DataFrame, row: int, col: int):
        """添加成交量柱状图"""
        try:
            # 计算颜色（红涨绿跌）
            colors = []
            for i in range(len(kline_data)):
                if i == 0:
                    colors.append('#ef5350')
                else:
                    if kline_data.iloc[i]['收盘'] >= kline_data.iloc[i-1]['收盘']:
                        colors.append('#ef5350')  # 红色（涨）
                    else:
                        colors.append('#26a69a')  # 绿色（跌）
            
            fig.add_trace(
                go.Bar(
                    x=kline_data['日期'],
                    y=kline_data['成交量'],
                    name='成交量',
                    marker_color=colors,
                    showlegend=False
                ),
                row=row, col=col
            )
        except Exception as e:
            self.logger.warning(f"添加成交量失败: {e}")
    
    def _create_empty_figure(self, stock_code: str, stock_name: str, height: int) -> go.Figure:
        """创建空图表"""
        fig = go.Figure()
        fig.add_annotation(
            text=f"暂无 {stock_code} {stock_name} 的K线数据",
            xref="paper",
            yref="paper",
            x=0.5,
            y=0.5,
            showarrow=False,
            font=dict(size=20, color="gray")
        )
        fig.update_layout(
            height=height,
            xaxis=dict(visible=False),
            yaxis=dict(visible=False),
            template='plotly_white'
        )
        return fig
    
    def get_kline_data(self, stock_code: str, days: int = 60, data_fetcher=None) -> Optional[pd.DataFrame]:
        """
        获取K线数据（带Tushare降级机制）
        
        Args:
            stock_code: 股票代码
            days: 获取天数
            data_fetcher: 数据获取器实例
            
        Returns:
            K线数据DataFrame
        """
        try:
            if data_fetcher is None:
                from smart_monitor_data import SmartMonitorDataFetcher
                data_fetcher = SmartMonitorDataFetcher()
            
            # 计算日期范围
            end_date = datetime.now().strftime('%Y%m%d')
            start_date = (datetime.now() - timedelta(days=days + 30)).strftime('%Y%m%d')  # 多取30天以确保足够数据
            
            # 方法1: 尝试使用AKShare获取（只尝试1次，避免IP封禁）
            try:
                import akshare as ak
                df = ak.stock_zh_a_hist(
                    symbol=stock_code,
                    period='daily',
                    start_date=start_date,
                    end_date=end_date,
                    adjust='qfq'
                )
                
                if df is not None and not df.empty:
                    # 只保留最近days天的数据
                    df = df.tail(days)
                    self.logger.info(f"✅ AKShare获取K线数据成功 {stock_code}，共{len(df)}条")
                    return df
                else:
                    self.logger.warning(f"AKShare未返回K线数据 {stock_code}，尝试降级到Tushare")
            except Exception as e:
                self.logger.warning(f"AKShare获取K线数据失败 {stock_code}: {type(e).__name__}, 尝试降级到Tushare")
            
            # 方法2: 降级到Tushare
            if data_fetcher and data_fetcher.ts_pro:
                self.logger.info(f"降级使用Tushare获取K线数据 {stock_code}")
                df = self._get_kline_from_tushare(stock_code, days, data_fetcher.ts_pro)
                if df is not None and not df.empty:
                    self.logger.info(f"✅ Tushare获取K线数据成功 {stock_code}，共{len(df)}条")
                    return df
            
            self.logger.error(f"所有数据源都无法获取K线数据 {stock_code}")
            return None
            
        except Exception as e:
            self.logger.error(f"获取K线数据失败 {stock_code}: {e}")
            import traceback
            self.logger.debug(traceback.format_exc())
            return None
    
    def _get_kline_from_tushare(self, stock_code: str, days: int, ts_pro) -> Optional[pd.DataFrame]:
        """
        从Tushare获取K线数据
        
        Args:
            stock_code: 股票代码
            days: 获取天数
            ts_pro: Tushare API实例
            
        Returns:
            K线数据DataFrame
        """
        try:
            # 转换股票代码格式
            if stock_code.startswith('6'):
                ts_code = f"{stock_code}.SH"
            elif stock_code.startswith(('0', '3')):
                ts_code = f"{stock_code}.SZ"
            else:
                ts_code = stock_code
            
            # 计算日期范围（多取一些确保足够）
            end_date = datetime.now().strftime('%Y%m%d')
            start_date = (datetime.now() - timedelta(days=days + 60)).strftime('%Y%m%d')
            
            # 获取日K线数据（前复权）
            df = ts_pro.daily(
                ts_code=ts_code,
                start_date=start_date,
                end_date=end_date,
                adj='qfq'
            )
            
            if df is None or df.empty:
                self.logger.error(f"Tushare未返回K线数据 {stock_code}")
                return None
            
            # Tushare数据是从新到旧，需要反转
            df = df.sort_values('trade_date', ascending=True).reset_index(drop=True)
            
            # 统一列名为AKShare格式
            df = df.rename(columns={
                'trade_date': '日期',
                'open': '开盘',
                'high': '最高',
                'low': '最低',
                'close': '收盘',
                'vol': '成交量',
                'amount': '成交额'
            })
            
            # 转换日期格式（Tushare: 20240115 -> 2024-01-15）
            df['日期'] = pd.to_datetime(df['日期'])
            
            # 只保留最近days天的数据
            df = df.tail(days)
            
            return df
            
        except Exception as e:
            self.logger.error(f"Tushare获取K线数据失败 {stock_code}: {type(e).__name__}: {str(e)}")
            return None


if __name__ == '__main__':
    # 测试代码
    logging.basicConfig(level=logging.INFO)
    
    kline = SmartMonitorKline()
    
    # 测试获取K线数据
    df = kline.get_kline_data('600519', days=60)
    
    if df is not None:
        print(f"获取到 {len(df)} 条K线数据")
        print(df.head())
        
        # 模拟AI决策
        ai_decisions = [
            {
                'decision_time': '2024-01-15 10:00:00',
                'action': 'buy',
                'confidence': 85,
                'reasoning': '技术指标良好，MACD金叉'
            },
            {
                'decision_time': '2024-01-20 14:30:00',
                'action': 'add_position',
                'confidence': 75,
                'reasoning': '突破关键压力位'
            },
            {
                'decision_time': '2024-01-25 11:00:00',
                'action': 'sell',
                'confidence': 80,
                'reasoning': 'RSI超买，建议止盈'
            }
        ]
        
        # 创建K线图
        fig = kline.create_kline_with_decisions(
            stock_code='600519',
            stock_name='贵州茅台',
            kline_data=df,
            ai_decisions=ai_decisions,
            show_volume=True,
            show_ma=True
        )
        
        # 保存为HTML
        fig.write_html('test_kline.html')
        print("K线图已保存到 test_kline.html")
    else:
        print("获取K线数据失败")

