"""LOF 基金相关 API 路由"""
import concurrent.futures
import logging
import re
import threading
import time

import pandas as pd
import requests
from fastapi import APIRouter, Query

from app.cache import update_cache_data, get_cached_lof_data
from app.services.fetcher import (
    fetch_spot_data,
    fetch_purchase_data,
    fetch_estimate_data,
    fetch_ths_kline,
    fetch_em_kline,
)
from app.utils.formatters import format_limit, format_amount

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/lof", tags=["LOF"])

# 小基金数据缓存（规模 < 3000万），主请求返回大基金后，前端通过剩余端点获取
remaining_cache = {"data": [], "lock": threading.Lock()}

# 后台刷新锁，避免同一时间多个请求同时触发刷新
_refresh_lock = threading.Lock()

# 基金类型缓存
_fund_type_cache: dict[str, str] = {}


def get_fund_type(fund_code: str) -> str:
    """获取基金类型（如 QDII、商品、混合型等），带缓存"""
    if fund_code in _fund_type_cache:
        return _fund_type_cache[fund_code]
    try:
        url = f"https://fundf10.eastmoney.com/jbgk_{fund_code}.html"
        r = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
        r.raise_for_status()
        m = re.search(r"基金类型</th>\s*<td>([^<]+)</td>", r.text)
        if m:
            fund_type = m.group(1).strip()
            _fund_type_cache[fund_code] = fund_type
            logger.info("基金 %s 类型：%s", fund_code, fund_type)
            return fund_type
    except Exception as e:
        logger.warning("获取基金 %s 类型失败：%s", fund_code, e)
    return ""


def fetch_em_realtime(fund_code: str) -> dict | None:
    """获取东方财富实时数据（最新价、成交量、成交额、f84）"""
    try:
        secid_prefix = "1" if fund_code.startswith(("5", "6", "9")) else "0"
        secid = f"{secid_prefix}.{fund_code}"
        url = "https://push2.eastmoney.com/api/qt/stock/get"
        params = {"secid": secid, "fields": "f43,f47,f48,f84"}
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            "Referer": "https://quote.eastmoney.com/",
        }
        r = requests.get(url, params=params, headers=headers, timeout=10)
        r.raise_for_status()
        d = r.json().get("data", {})
        return {
            "price": float(d.get("f43", 0)) / 1000,
            "volume": float(d.get("f47", 0)),  # 手
            "turnover": float(d.get("f48", 0)),  # 元
            "f84": float(d.get("f84", 0)),  # 股
        }
    except Exception as e:
        logger.warning("东方财富实时数据获取失败：%s", e)
    return None


@router.get("/history")
def get_lof_history(
    fund_code: str = Query(..., description="基金代码"),
    fund_name: str = Query("", description="基金名称"),
):
    """获取 LOF 基金历史数据（价格 + 净值 + 溢价率 + 成交额 + 场内份额）
    主数据源：同花顺 K-line（换手率基于场内份额，可直接算出场内份额）
    备用数据源：东方财富 K-line
    净值数据：东方财富 lsjz 接口
    """
    try:
        logger.info("获取基金 %s 历史数据...", fund_code)

        # 1. 获取历史行情（优先同花顺，备用东方财富）
        price_df = fetch_ths_kline(fund_code)
        source = "ths"
        secid = None
        if price_df is None:
            logger.warning("同花顺获取失败，尝试东方财富备用...")
            secid_prefix = "1" if fund_code.startswith(("5", "6", "9")) else "0"
            secid = f"{secid_prefix}.{fund_code}"
            price_df = fetch_em_kline(fund_code, secid)
            source = "em"
            if price_df is None:
                return {"code": 404, "msg": f"未找到基金 {fund_code} 的历史价格数据"}

        rt_data = None

        # 用东方财富实时数据校准最新一天（同花顺 year.js 最新一天可能缓存未更新）
        if source == "ths" and not price_df.empty:
            try:
                rt_data = fetch_em_realtime(fund_code)
                if rt_data:
                    last_idx = price_df.index[-1]
                    last_date = price_df.loc[last_idx, "date"]
                    today = pd.Timestamp.now().normalize()
                    # 只有同花顺最新一天是今天，才用实时数据校准（盘中缓存数据可能异常）
                    if last_date == today:
                        ths_price = price_df.loc[last_idx, "price"]
                        ths_turnover = price_df.loc[last_idx, "turnover"]
                        price_diff = abs(ths_price - rt_data["price"]) / ths_price if ths_price > 0 else 0
                        turnover_diff = abs(ths_turnover - rt_data["turnover"] / 10000) / (ths_turnover or 1)
                        if price_diff > 0.001 or turnover_diff > 0.5:
                            logger.info(
                                "基金 %s 同花顺最新一天数据异常，用东方财富校准: 价格 %.3f->%.3f, 成交额 %.2f->%.2f",
                                fund_code, ths_price, rt_data["price"], ths_turnover, rt_data["turnover"] / 10000,
                            )
                            price_df.loc[last_idx, "price"] = rt_data["price"]
                            price_df.loc[last_idx, "volume"] = rt_data["volume"]
                            price_df.loc[last_idx, "turnover"] = round(rt_data["turnover"] / 10000, 2)
            except Exception as e:
                logger.warning("实时数据校准失败：%s", e)

        # 份额数据与日终结算一致，延后一天显示（T日收盘后结算，T+1日公布）
        price_df["share_volume"] = price_df["share_volume"].shift(1)

        # 同花顺数据源：对最新一天的 share_volume 用 f84 校准/填充
        # 同花顺换手率只保留3位小数，低换手率基金（如161226）份额计算误差可达~40万份
        # f84 是东方财富实时总份额，对LOF基金通常≈场内份额，误差<1%，可用来校准最新一天
        if source == "ths" and rt_data and rt_data.get("f84"):
            try:
                last_idx = price_df.index[-1]
                f84_share = round(rt_data["f84"] / 10000, 2)
                current_share = price_df.loc[last_idx, "share_volume"]
                if pd.isna(current_share):
                    price_df.loc[last_idx, "share_volume"] = f84_share
                    logger.info("基金 %s 最新一天份额用 f84 填充: %.2f", fund_code, f84_share)
                elif current_share > 0 and abs(current_share - f84_share) / current_share < 0.05:
                    price_df.loc[last_idx, "share_volume"] = f84_share
                    logger.info("基金 %s 最新一天份额用 f84 校准: %.2f -> %.2f", fund_code, current_share, f84_share)
            except Exception as e:
                logger.warning("f84 填充份额失败：%s", e)

        # 如果是东方财富数据源，用 f84 填充最新一天 NaN share_volume
        if source == "em":
            try:
                qt_url = "https://push2.eastmoney.com/api/qt/stock/get"
                qt_params = {"secid": secid, "fields": "f84"}
                qt_headers = {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
                    "Referer": "https://quote.eastmoney.com/",
                }
                r = requests.get(qt_url, params=qt_params, headers=qt_headers, timeout=10)
                r.raise_for_status()
                qt_data = r.json()
                f84 = qt_data.get("data", {}).get("f84")
                if f84 is not None:
                    real_time_share = round(float(f84) / 10000, 2)
                    last_idx = price_df.index[-1]
                    if not price_df.empty and pd.isna(price_df.loc[last_idx, "share_volume"]):
                        price_df.loc[last_idx, "share_volume"] = real_time_share
                        logger.info("基金 %s 份额用 f84 填充：%.2f (万份)", fund_code, real_time_share)
            except Exception as e:
                logger.warning("实时份额校准失败：%s", e)

        # 计算场内新增和份额涨幅
        price_df["change_amount"] = (price_df["share_volume"] - price_df["share_volume"].shift(1)).round(2)
        price_df["change_pct"] = ((price_df["change_amount"] / price_df["share_volume"].shift(1)) * 100).round(3)

        # 2. 获取历史净值（东方财富 lsjz 接口）
        nav_url = "https://api.fund.eastmoney.com/f10/lsjz"
        nav_params = {
            "fundCode": fund_code,
            "pageIndex": "1",
            "pageSize": "120",
            "startDate": price_df["date"].min().strftime("%Y-%m-%d"),
            "endDate": price_df["date"].max().strftime("%Y-%m-%d"),
        }
        nav_headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
            "Referer": "https://fund.eastmoney.com/",
        }

        nav_rows = []
        total_count = 0
        page = 1
        while True:
            nav_params["pageIndex"] = str(page)
            r = requests.get(nav_url, params=nav_params, headers=nav_headers, timeout=30)
            r.raise_for_status()
            nav_json = r.json()
            if page == 1:
                total_count = nav_json.get("TotalCount", 0)
            nav_list = nav_json.get("Data", {}).get("LSJZList", [])
            if not nav_list:
                break
            for item in nav_list:
                dwjz = item.get("DWJZ", "")
                nav_rows.append({
                    "nav_date": item["FSRQ"],
                    "nav": float(dwjz) if dwjz else None,
                })
            page += 1
            if len(nav_rows) >= total_count:
                break

        nav_df = pd.DataFrame(nav_rows) if nav_rows else pd.DataFrame(columns=["nav_date", "nav"])
        nav_df["nav_date"] = pd.to_datetime(nav_df["nav_date"])

        # 3. 合并价格和净值
        # 判断基金类型决定净值匹配策略：QDII 净值延迟，用 T-1；非 QDII 用当天
        fund_type = get_fund_type(fund_code)
        is_qdii = "QDII" in fund_type
        nav_df = nav_df.sort_values("nav_date").dropna(subset=["nav"])

        if is_qdii:
            # QDII：T日价格对比T-1日净值（净值公布延迟）
            price_df["prev_date"] = price_df["date"].shift(1)
            merged = price_df.merge(
                nav_df[["nav_date", "nav"]],
                left_on="prev_date",
                right_on="nav_date",
                how="left"
            )
            merged["nav_date"] = merged["prev_date"].apply(
                lambda x: x.strftime("%Y-%m-%d") if pd.notna(x) else None
            )
        else:
            # 非 QDII（商品、混合型等）：T日价格对比T日净值（当天有就显示，没有就空）
            merged = price_df.merge(
                nav_df[["nav_date", "nav"]],
                left_on="date",
                right_on="nav_date",
                how="left"
            )
            merged["nav_date"] = merged["date"].apply(
                lambda x: x.strftime("%Y-%m-%d") if pd.notna(x) else None
            )

        # 4. 计算溢价率
        merged["premium_rate"] = (
            (merged["price"] - merged["nav"]) / merged["nav"] * 100
        ).round(2)

        # 5. 格式化输出
        merged["date"] = merged["date"].dt.strftime("%Y-%m-%d")

        result = merged[["date", "price", "nav_date", "nav", "premium_rate",
                         "turnover", "share_volume", "change_amount", "change_pct"]].copy()
        result.columns = ["date", "price", "navDate", "nav", "premiumRate",
                          "turnover", "shareVolume", "changeAmount", "changePct"]

        # 处理 NaN（确保 float NaN 也被替换为 None，避免 JSON 序列化失败）
        result = result.where(pd.notnull(result), None)
        result = result.replace({pd.NA: None, float('nan'): None})
        # 逐行逐列确保彻底清除 NaN
        data = []
        for record in result.to_dict(orient="records"):
            clean = {}
            for k, v in record.items():
                if isinstance(v, float) and (v != v):  # NaN check
                    clean[k] = None
                else:
                    clean[k] = v
            data.append(clean)
        data.reverse()  # 最新的在前

        logger.info("基金 %s 历史数据返回成功，共 %d 条", fund_code, len(data))
        return {"code": 200, "data": data, "fundCode": fund_code, "fundName": fund_name}

    except Exception as e:
        logger.exception("获取历史数据失败")
        return {"code": 500, "msg": f"获取历史数据失败：{str(e)}"}


@router.get("")
def get_lof_data():
    """获取 LOF 实时数据 + 溢价率 + 限额
    采用 stale-while-revalidate 策略：
    1. 有缓存 → 立即返回（毫秒级），后台异步刷新
    2. 无缓存 → 等待首次获取（约 5-8 秒）
    """
    # 检查是否有缓存
    cached = get_cached_lof_data()
    if cached is not None:
        # 尝试后台异步刷新（非阻塞）
        if _refresh_lock.acquire(blocking=False):
            try:
                threading.Thread(target=_refresh_data_background, daemon=True).start()
            except Exception:
                _refresh_lock.release()
        logger.info("返回缓存数据，缓存时间：%s", cached["time"])
        return {
            "code": 200,
            "data": cached["data"],
            "hasMore": cached.get("hasMore", False)
        }

    # 无缓存：同步等待首次获取
    logger.info("首次启动，同步获取 LOF 数据...")
    result = _do_fetch_data()
    return result


def _refresh_data_background():
    """后台刷新数据"""
    try:
        logger.info("后台刷新 LOF 数据...")
        result = _do_fetch_data()
        logger.info("后台刷新完成")
    except Exception as e:
        logger.error("后台刷新失败：%s", e)
    finally:
        _refresh_lock.release()


def _do_fetch_data():
    """核心数据获取逻辑"""
    try:
        logger.info("开始获取 LOF 数据...")

        # 1. 并行获取三个数据源（串行→并行，总耗时从 ~9s 降至 ~3-4s）
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            future_spot = executor.submit(fetch_spot_data)
            future_purchase = executor.submit(fetch_purchase_data)
            future_estimate = executor.submit(fetch_estimate_data)

            spot = future_spot.result(timeout=30)
            logger.info("LOF 实时数据获取成功，共 %d 条", len(spot))

            purchase = future_purchase.result(timeout=30)
            logger.info("基金净值/限额数据获取成功，共 %d 条", len(purchase))

            estimate = future_estimate.result(timeout=30)
            logger.info("基金估算净值获取成功（仅LOF），共 %d 条", len(estimate))

        # 1.5 提取 LOF 代码列表，提前过滤以减少后续合并计算量
        lof_codes = set(spot["代码"].astype(str).tolist())
        purchase = purchase[purchase["基金代码"].astype(str).isin(lof_codes)]
        estimate = estimate[estimate["基金代码"].astype(str).isin(lof_codes)]
        logger.info("过滤后：净值/限额 %d 条，估算净值 %d 条", len(purchase), len(estimate))

        # 2. 检查数据有效性：如果所有价格都是无效值，可能是非交易时段 API 返回 '-'
        # 此时应保留已有缓存，避免用无效数据覆盖
        spot_prices = pd.to_numeric(spot["最新价"], errors="coerce")
        valid_count = spot_prices.notna().sum()
        has_valid_data = valid_count >= 10
        if not has_valid_data:
            cached = get_cached_lof_data()
            if cached is not None:
                logger.warning("有效价格数据仅 %d 条，保留缓存", valid_count)
                return {"code": 200, "data": cached["data"], "hasMore": cached.get("hasMore", False)}
            logger.warning("有效价格数据仅 %d 条，无缓存可用，仍使用当前数据", valid_count)

        # 3. 合并数据
        df = spot.merge(
            purchase,
            left_on="代码",
            right_on="基金代码",
            how="left"
        ).merge(
            estimate,
            left_on="代码",
            right_on="基金代码",
            how="left"
        )

        # 3.5 昨日溢价率用昨收计算（固定不变），实时溢价率用当前价
        df["最新价"] = pd.to_numeric(df["最新价"], errors="coerce")
        df["昨收"] = pd.to_numeric(df["昨收"], errors="coerce")
        has_current_price = df["最新价"].notna() & (df["最新价"] != 0)

        # 4. 计算溢价率
        # 昨日溢价率 = (昨收 - 最新净值) / 最新净值 —— 固定不变
        df["溢价率"] = (
            (df["昨收"] - df["最新净值/万份收益"])
            / df["最新净值/万份收益"]
            * 100
        ).round(2)

        # 实时溢价率 = (当前价 - 估算净值) / 估算净值 —— 非交易时段无效
        df["估算溢价率"] = (
            (df["最新价"] - df["估算净值"])
            / df["估算净值"]
            * 100
        ).round(2)
        # 当前价无效时实时溢价率也无效
        df.loc[~has_current_price, "估算溢价率"] = None

        # 实时溢价率2 - 按文章第三/四点算法：
        # 被动指数QDII-LOF(三): 估算净值 = T-2净值 × (1+指数涨跌幅) × T日汇率/T-1日汇率
        # 主动管理LOF(四): 估算净值 = T-1净值 × (1+基准涨跌幅)
        # 实现：用涨跌幅作为市场变动代理，调整过时的最新净值
        # 调整净值 = 最新净值 × (1 + 涨跌幅/100)
        # 溢价率2 = (当前价 - 调整净值) / 调整净值 × 100
        df["估算溢价率2"] = None
        valid_for2 = has_current_price & df["最新净值/万份收益"].notna() & (df["最新净值/万份收益"] > 0)
        if valid_for2.any():
            adjusted_nav = (
                df.loc[valid_for2, "最新净值/万份收益"]
                * (1 + df.loc[valid_for2, "涨跌幅"].fillna(0) / 100)
            )
            df.loc[valid_for2, "估算溢价率2"] = (
                (df.loc[valid_for2, "最新价"] - adjusted_nav) / adjusted_nav * 100
            ).round(2)

        # 5. 按基金规模拆分：大基金（≥3000万）优先返回，小基金后台缓存
        LARGE_FUND_THRESHOLD = 30000000
        large_mask = (df["总市值"] >= LARGE_FUND_THRESHOLD) | (df["总市值"].isna()) | (df["总市值"] <= 0)
        df_large = df[large_mask].copy()
        df_small = df[~large_mask].copy()

        def format_df(part: pd.DataFrame) -> list[dict]:
            part["限额"] = part["日累计限定金额"].apply(format_limit)
            part["总市值_格式化"] = part["总市值"].apply(format_amount)
            part["成交额_格式化"] = part["成交额"].apply(format_amount)
            part["赎回状态"] = part["赎回状态"].fillna("")
            part = part[[
                "代码", "名称", "最新价", "涨跌幅",
                "最新净值/万份收益", "估算净值", "溢价率", "估算溢价率", "估算溢价率2",
                "限额", "申购状态", "赎回状态",
                "总市值_格式化", "成交量", "成交额_格式化"
            ]]
            part.columns = [
                "fundCode", "fundName", "tradePrice", "increaseRate",
                "netValue", "estimateValue", "premiumRate", "estimatePremiumRate", "estimatePremiumRate2",
                "purchaseLimit", "purchaseStatus", "redemptionStatus",
                "fundSize", "volume", "turnover"
            ]
            part = part.replace({pd.NA: "-"})
            part = part.where(pd.notnull(part), "-")
            return part.to_dict(orient="records")

        large_data = format_df(df_large)
        small_data = format_df(df_small)

        with remaining_cache["lock"]:
            remaining_cache["data"] = small_data

        update_cache_data(large_data, small_data, has_valid_data=has_valid_data)
        logger.info("数据返回成功，大基金 %d 条，小基金 %d 条", len(large_data), len(small_data))
        return {"code": 200, "data": large_data, "hasMore": len(small_data) > 0}

    except concurrent.futures.TimeoutError:
        logger.error("请求数据源超时（超过 30 秒）")
        cached = get_cached_lof_data()
        if cached:
            logger.info("返回缓存数据，缓存时间：%s", cached["time"])
            return {"code": 200, "data": cached["data"], "cached": True}
        return {"code": 500, "msg": "数据获取超时，请稍后重试"}

    except Exception as e:
        logger.exception("数据获取失败")
        cached = get_cached_lof_data()
        if cached:
            logger.info("返回缓存数据，缓存时间：%s", cached["time"])
            return {"code": 200, "data": cached["data"], "cached": True}
        return {"code": 500, "msg": f"数据获取失败：{str(e)}"}


@router.get("/remaining")
def get_remaining_data():
    """获取剩余的小基金数据（规模 < 3000万），主数据返回后前端异步获取"""
    # 优先返回刚获取的小基金数据
    with remaining_cache["lock"]:
        if remaining_cache["data"]:
            data = remaining_cache["data"]
            remaining_cache["data"] = []
            return {"code": 200, "data": data}
    # 后备：返回缓存中的小基金数据
    from app.cache import get_cached_small_data
    small = get_cached_small_data()
    return {"code": 200, "data": small}