# coding:utf-8
import time
import datetime
import logging
import os
import sys
from xtquant import xtdata
from xtquant.xttrader import XtQuantTrader
from xtquant.xttype import StockAccount

# 导入自定义模块
from config_loader import get_qmt_config, get_logging_config, get_strategy_config
from strategy import load_initial_positions, create_buy_strategy_callback, create_sell_strategy_callback, \
    create_clearance_strategy_callback, start_account_funds_monitor, start_reconciliation_monitor, \
    start_position_snapshot_monitor, refresh_positions_snapshot
from redis_state_manager import RedisStateManager
from database_manager import DatabaseManager, close_pool
from trader_callback import MyXtQuantTraderCallback

def setup_logging():
    """设置日志配置"""
    log_config = get_logging_config()
    
    # 创建logs目录（如果不存在）
    log_dir = os.path.join(os.path.dirname(__file__), '..', 'logs')
    os.makedirs(log_dir, exist_ok=True)
    
    # 生成日志文件名（包含日期时间）
    log_filename = f"qmt_auto_buy_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
    log_filepath = os.path.join(log_dir, log_filename)
    
    # 配置日志格式
    logging.basicConfig(
        level=getattr(logging, log_config['level']),
        format=log_config['log_format'],
        handlers=[
            logging.FileHandler(log_filepath, encoding='utf-8'),
            logging.StreamHandler(sys.stdout)  # 同时输出到控制台
        ]
    )
    
    logger = logging.getLogger(__name__)
    logger.info(f"日志文件已创建: {log_filepath}")
    return logger

def interact():
    """执行后进入repl模式"""
    import code
    code.InteractiveConsole(locals=globals()).interact()

if __name__ == '__main__':
    # 初始化日志
    logger = setup_logging()
    
    # 获取配置
    qmt_config = get_qmt_config()
    strategy_config = get_strategy_config()
    buy_amount = strategy_config.get('buy', {}).get('amount', 50000)
    
    logger.info("启动基于数据库的自动买入卖出策略")
    logger.info("="*50)
    
    # 初始化计划：先从MySQL拉取“已发布计划”覆盖写入到Redis（一次性），然后读取Redis供订阅
    rsm = RedisStateManager(logger)
    try:
        dbm = DatabaseManager(logger)
        db_buy = dbm.get_active_buy_plans(strategy_id=1)
        db_sell = dbm.get_active_sell_plans(strategy_id=1)
        # 清空Redis计划并全量覆盖
        rsm.clear_buy_plans()
        rsm.clear_sell_plans()
        for code, plan in db_buy.items():
            # 兼容数据库返回结构
            rsm.set_buy_plan(code, {
                'stock_name': plan.get('stock_name'),
                'target_price': plan['target_price'],
                'buy_amount': plan['buy_amount'],
                'is_active': 1
            })
        for code, plan in db_sell.items():
            rsm.set_sell_plan(code, {
                'stock_name': plan.get('stock_name'),
                'target_price': plan['target_price'],
                'sell_quantity': plan.get('sell_quantity'),
                'is_active': 1
            })
        logger.info(f"已用MySQL已发布计划初始化Redis，买入{len(db_buy)}，卖出{len(db_sell)}")
        # 设置已同步版本
        try:
            max_ver = dbm.get_max_plan_version(strategy_id=1)
            rsm.set_synced_version(max_ver)
            logger.info(f"初始化同步版本设置为 {max_ver}")
        except Exception as _e:
            logger.warning(f"初始化同步版本设置失败: {_e}")
    except Exception as e:
        logger.warning(f"初始化Redis计划失败（将使用已有Redis计划）：{str(e)}")

    buy_plans = rsm.get_all_buy_plans()
    sell_plans = rsm.get_all_sell_plans()
    
    # 加载清仓计划（启动时一次性加载）
    try:
        dbm = DatabaseManager(logger)
        clearance_requests = dbm.get_pending_clearance_requests()
        # 清空Redis清仓计划并全量覆盖
        rsm.clear_clearance_plans()
        for code, request in clearance_requests.items():
            rsm.set_clearance_plan(code, request)
        logger.info(f"已从MySQL加载清仓计划到Redis，共 {len(clearance_requests)} 条")
    except Exception as e:
        logger.warning(f"加载清仓计划失败: {str(e)}")
        clearance_requests = {}
    
    clearance_plans = rsm.get_all_clearance_plans()
    
    logger.info(f"买入计划数量(Redis): {len(buy_plans)}")
    logger.info(f"卖出计划数量(Redis): {len(sell_plans)}")
    logger.info(f"清仓计划数量(Redis): {len(clearance_plans)}")
    # logger.info(f"每只股票买入金额: {buy_amount}元")
    logger.info("="*50)
    
    # 显示买入计划
    if buy_plans:
        logger.info("买入计划:")
        for stock_code, plan_info in buy_plans.items():
            logger.info(f"  {stock_code} ({plan_info['stock_name']}): 目标价格 {plan_info['target_price']:.2f}, 买入金额 {plan_info['buy_amount']:.2f}元")
    
    # 显示卖出计划
    if sell_plans:
        logger.info("卖出计划:")
        for stock_code, plan_info in sell_plans.items():
            sell_qty = plan_info['sell_quantity'] if plan_info['sell_quantity'] else '全部'
            logger.info(f"  {stock_code} ({plan_info['stock_name']}): 目标价格 {plan_info['target_price']:.2f}, 卖出数量 {sell_qty}")
    
    # 显示清仓计划
    if clearance_plans:
        logger.info("清仓计划:")
        for stock_code, plan_info in clearance_plans.items():
            reason = plan_info.get('reason', '无')
            logger.info(f"  {stock_code} ({plan_info.get('stock_name', '')}): 清仓原因 {reason}")
    
    if not buy_plans and not sell_plans and not clearance_plans:
        logger.warning("警告：买入、卖出和清仓计划都为空！请检查数据库配置。")
    
    logger.info("="*50)
    
    # 从配置文件获取客户端路径
    client_path = qmt_config['client_path']
    logger.info(f"客户端路径: {client_path}")
    
    # 生成session id 整数类型 同时运行的策略不能重复
    session_id = int(time.time())
    
    xt_trader = XtQuantTrader(client_path, session_id)
    # 开启主动请求接口的专用线程 开启后在on_stock_xxx回调函数里调用XtQuantTrader.query_xxx函数不会卡住回调线程，但是查询和推送的数据在时序上会变得不确定
    # 详见: http://docs.thinktrader.net/vip/pages/ee0e9b/#开启主动请求接口的专用线程
    # xt_trader.set_relaxed_response_order_enabled(True)

    # 从配置文件获取账户信息
    account_id = qmt_config['account']['account_id']
    account_type = qmt_config['account']['account_type']
    
    # 创建资金账号对象
    acc = StockAccount(account_id, account_type)
    logger.info(f"账户信息: {account_id} ({account_type})")
    
    # 创建交易回调类对象，并声明接收回调
    callback = MyXtQuantTraderCallback(logger)
    xt_trader.register_callback(callback)
    # 启动交易线程
    xt_trader.start()
    # 建立交易连接，返回0表示连接成功
    connect_result = xt_trader.connect()
    logger.info(f'建立交易连接，返回0表示连接成功 {connect_result}')
    # 对交易回调进行订阅，订阅后可以收到交易主推，返回0表示订阅成功
    subscribe_result = xt_trader.subscribe(acc)
    logger.info(f'对交易回调进行订阅，订阅后可以收到交易主推，返回0表示订阅成功 {subscribe_result}')

    # 查询账户资金
    account_info = xt_trader.query_stock_asset(acc)
    print(account_info)
    print("=========================================")
    available_cash = account_info.m_dCash
    logger.info(f"账户可用资金: {available_cash:.2f}元")

    # 启动时一次性加载并同步持仓：券商→Redis
    initial_positions = load_initial_positions(xt_trader, acc, logger)
    logger.info(f"初始持仓: {initial_positions}")
    # 覆盖式同步DB未完成订单到Redis
    try:
        rsm = RedisStateManager(logger)
        dbm = DatabaseManager(logger)
        open_orders = dbm.get_open_orders()
        rsm.clear_pending()
        for stock_code, info in open_orders.items():
            rsm.set_pending(stock_code, info)
        logger.info(f"已用DB未完成订单覆盖Redis在途订单，共 {len(open_orders)} 条")
    except Exception as e:
        logger.warning(f"Redis状态读取失败: {str(e)}")

    # 启动账户资金监控（每小时更新一次）
    start_account_funds_monitor(xt_trader, acc, logger)

    # 停用订单超时监控（策略为价格触发挂单，挂单后不自动撤单）
    logger.info("已停用订单超时监控与自动撤单逻辑：挂单成功后将持续等待成交")

    # 启动每10分钟对账线程（兜底：无回调或程序中断时），可在配置里调整频率
    start_reconciliation_monitor(xt_trader, acc, logger, interval_seconds=600)

    # 启动每10分钟持仓快照覆盖更新线程（只更新DB，不叠加）
    start_position_snapshot_monitor(xt_trader, acc, logger, interval_seconds=600)
    # 下载股票数据
    # xtdata.download_sector_data()

    # 创建买入策略回调函数
    buy_strategy_callback = create_buy_strategy_callback(xt_trader, acc, buy_amount, logger)
    
    # 创建卖出策略回调函数
    sell_strategy_callback = create_sell_strategy_callback(xt_trader, acc, logger)
    
    # 创建清仓策略回调函数
    clearance_strategy_callback = create_clearance_strategy_callback(xt_trader, acc, logger)

    # 订阅买卖计划和清仓计划中的股票行情（基于Redis）
    subscription_ids = {}  # 统一管理所有订阅ID
    
    if buy_plans:
        buy_code_list = list(buy_plans.keys())
        logger.info(f"开始订阅 {len(buy_code_list)} 只买入股票的行情...")
        for code in buy_code_list:
            sub_id = xtdata.subscribe_quote(code, '1d', callback=buy_strategy_callback)
            subscription_ids[code] = sub_id
            logger.info(f"已订阅买入 {code} ({buy_plans[code].get('stock_name','')}) 行情，订阅ID: {sub_id}")
    if sell_plans:
        sell_code_list = list(sell_plans.keys())
        logger.info(f"开始订阅 {len(sell_code_list)} 只卖出股票的行情...")
        for code in sell_code_list:
            sub_id = xtdata.subscribe_quote(code, '1d', callback=sell_strategy_callback)
            subscription_ids[code] = sub_id
            logger.info(f"已订阅卖出 {code} ({sell_plans[code].get('stock_name','')}) 行情，订阅ID: {sub_id}")
    if clearance_plans:
        clearance_code_list = list(clearance_plans.keys())
        logger.info(f"开始订阅 {len(clearance_code_list)} 只清仓股票的行情...")
        for code in clearance_code_list:
            # 如果已经订阅过（买入或卖出），跳过
            if code not in subscription_ids:
                sub_id = xtdata.subscribe_quote(code, '1d', callback=clearance_strategy_callback)
                subscription_ids[code] = sub_id
                logger.info(f"已订阅清仓 {code} ({clearance_plans[code].get('stock_name','')}) 行情，订阅ID: {sub_id}")
            else:
                logger.info(f"清仓股票 {code} 已在其他策略中订阅，跳过重复订阅")

    # 计划同步线程：每60秒从MySQL同步到Redis，并直接更新订阅（不再分线程管理订阅）
    import threading, time
    def plan_sync_worker():
        interval = 60  # 固定60秒同步一次
        # 当前已订阅集合
        subscribed_codes = set(subscription_ids.keys())
        # 存储订阅ID的字典，初始化为初始订阅ID
        current_subscription_ids = subscription_ids.copy()
        
        while True:
            try:
                time.sleep(interval)
                dbm_local = DatabaseManager(logger)
                # 拉取当日最新激活计划
                latest_buy = dbm_local.get_active_buy_plans(strategy_id=1)
                latest_sell = dbm_local.get_active_sell_plans(strategy_id=1)
                # 对比Redis，找出新增/删除/变更（版本或数值变化）
                curr_buy = rsm.get_all_buy_plans()
                curr_sell = rsm.get_all_sell_plans()
                latest_buy_codes = set(latest_buy.keys())
                latest_sell_codes = set(latest_sell.keys())
                # 新增/删除
                add_buy = latest_buy_codes - set(curr_buy.keys())
                del_buy = set(curr_buy.keys()) - latest_buy_codes
                add_sell = latest_sell_codes - set(curr_sell.keys())
                del_sell = set(curr_sell.keys()) - latest_sell_codes
                # 变更（比较 trading_version 或关键字段）
                changed_buy = set()
                for code in (latest_buy_codes & set(curr_buy.keys())):
                    lb = latest_buy[code]
                    cb = curr_buy[code]
                    if (
                        lb.get('trading_version', 0) != cb.get('trading_version', 0)
                        or lb['target_price'] != cb.get('target_price')
                        or lb['buy_amount'] != cb.get('buy_amount')
                    ):
                        changed_buy.add(code)
                changed_sell = set()
                for code in (latest_sell_codes & set(curr_sell.keys())):
                    ls = latest_sell[code]
                    cs = curr_sell[code]
                    if (
                        ls.get('trading_version', 0) != cs.get('trading_version', 0)
                        or ls['target_price'] != cs.get('target_price')
                        or (ls.get('sell_quantity') != cs.get('sell_quantity'))
                    ):
                        changed_sell.add(code)
                # 更新Redis
                for code in add_buy | changed_buy:
                    rsm.set_buy_plan(code, latest_buy[code])
                for code in add_sell | changed_sell:
                    rsm.set_sell_plan(code, latest_sell[code])
                for code in del_buy:
                    rsm.del_buy_plan(code)
                for code in del_sell:
                    rsm.del_sell_plan(code)
                # 直接同步订阅（差分）
                new_codes = latest_buy_codes | latest_sell_codes
                
                # 新增订阅
                for code in new_codes - subscribed_codes:
                    # 判断是买入还是卖出计划
                    if code in latest_buy_codes:
                        sub_id = xtdata.subscribe_quote(code, '1d', callback=buy_strategy_callback)
                        logger.info(f"[订阅新增-买入] {code}，订阅ID: {sub_id}")
                    else:
                        sub_id = xtdata.subscribe_quote(code, '1d', callback=sell_strategy_callback)
                        logger.info(f"[订阅新增-卖出] {code}，订阅ID: {sub_id}")
                    current_subscription_ids[code] = sub_id
                
                # 取消订阅
                for code in subscribed_codes - new_codes:
                    if code in current_subscription_ids:
                        xtdata.unsubscribe_quote(current_subscription_ids[code])
                        del current_subscription_ids[code]
                        logger.info(f"[取消订阅] {code}")
                
                subscribed_codes = new_codes
                # logger.info("计划同步与订阅更新完成")
            except Exception as e:
                logger.warning(f"计划同步线程异常: {str(e)}")

    threading.Thread(target=plan_sync_worker, daemon=True).start()

    logger.info("策略启动完成，等待行情推送...")
    if buy_plans:
        logger.info("买入策略：当股票价格低于目标价格时，将自动买入指定金额")
    if sell_plans:
        logger.info("卖出策略：当股票价格高于目标价格时，将自动卖出指定数量")
    if clearance_plans:
        logger.info("清仓策略：开盘时（连续交易时段）自动全仓卖出指定股票")
    logger.info("按 Ctrl+C 停止策略")

    # 阻塞主线程退出
    try:
        xt_trader.run_forever()
    except KeyboardInterrupt:
        logger.info("策略已停止")
    finally:
        # 程序退出时清理连接池
        try:
            close_pool()
            logger.info("数据库连接池已清理")
        except Exception as e:
            logger.error(f"清理数据库连接池失败: {str(e)}")
    
    # 如果使用vscode pycharm等本地编辑器 可以进入交互模式 方便调试 （把上一行的run_forever注释掉 否则不会执行到这里）
    # interact() 