"""
Documentation-based E2E tests for AKShare CLI harness.

Tests are derived from the official akshare documentation:
    https://akshare.akfamily.xyz/data/index.html

Each test calls a real akshare function via the CLI and validates:
    1. Exit code is 0 (success)
    2. JSON output is parseable
    3. Returned data has expected structure (total_rows, columns, data)

All tests require network access and are marked with @pytest.mark.network.
Run with:  pytest -m network test_doc_examples.py -v
Skip with: pytest -m "not network" ...
"""

import json
import os
import shutil
import subprocess
import sys
import tempfile

import pytest


# ─── Shared helpers ──────────────────────────────────────────────────────────


def _resolve_cli(name: str = "akshare-cli") -> str:
    """Resolve CLI path: venv -> system PATH -> module fallback."""
    # Try venv first
    venv_bin = os.path.join(
        os.path.dirname(os.path.dirname(os.path.dirname(
            os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        ))),
        ".venv", "bin", name,
    )
    if os.path.isfile(venv_bin):
        return venv_bin
    path = shutil.which(name)
    if path:
        return path
    return None


def run_cli(args: list, timeout: int = 60) -> subprocess.CompletedProcess:
    """Run CLI and return result."""
    cli_path = _resolve_cli()
    if cli_path:
        cmd = [cli_path] + args
    else:
        cmd = [sys.executable, "-m", "akshare_cli.cli"] + args
    return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)


def run_json(args: list, timeout: int = 60) -> dict:
    """Run CLI with --json and return parsed JSON dict."""
    result = run_cli(["--json"] + args, timeout=timeout)
    assert result.returncode == 0, (
        f"CLI failed (exit {result.returncode}): {result.stdout[:500]} {result.stderr[:500]}"
    )
    # Strip any progress bar lines (tqdm output on stdout)
    stdout = result.stdout.strip()
    # Find the first '{' which marks the start of JSON
    idx = stdout.find("{")
    assert idx != -1, f"No JSON object found in output: {stdout[:300]}"
    return json.loads(stdout[idx:])


def assert_dataframe_json(data: dict, min_rows: int = 1):
    """Assert that the JSON output has standard DataFrame structure."""
    assert "total_rows" in data, f"Missing 'total_rows' in: {list(data.keys())}"
    assert "columns" in data, f"Missing 'columns' in: {list(data.keys())}"
    assert "data" in data, f"Missing 'data' in: {list(data.keys())}"
    assert data["total_rows"] >= min_rows, (
        f"Expected >= {min_rows} rows, got {data['total_rows']}"
    )
    assert len(data["columns"]) > 0, "No columns returned"
    assert len(data["data"]) >= min_rows, (
        f"Expected >= {min_rows} data entries, got {len(data['data'])}"
    )


# ─── Stock Tests ─────────────────────────────────────────────────────────────


@pytest.mark.network
class TestStockCommands:
    """Test stock-related CLI commands from documentation."""

    def test_stock_sse_summary(self):
        """上海证券交易所-股票数据总貌"""
        data = run_json(["call", "stock_sse_summary"])
        assert_dataframe_json(data)
        assert "项目" in data["columns"] or "item" in str(data["columns"]).lower()

    def test_stock_szse_summary(self):
        """深圳证券交易所-市场总貌"""
        data = run_json(["call", "stock_szse_summary", "--date", "20200619"])
        assert_dataframe_json(data)

    def test_stock_individual_info_em(self):
        """东方财富-个股信息查询 (000001 平安银行)"""
        data = run_json(["call", "stock_individual_info_em", "--symbol", "000001"])
        assert_dataframe_json(data)
        # Check that it contains stock code info
        items = [row.get("item", "") for row in data["data"]]
        assert any("股票" in str(item) or "代码" in str(item) for item in items), (
            f"Expected stock code info in items: {items}"
        )

    def test_stock_bid_ask_em(self):
        """东方财富-五档盘口"""
        data = run_json(["call", "stock_bid_ask_em", "--symbol", "000001"])
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_zh_a_spot_em(self):
        """东方财富-A股实时行情 (slow: 5000+ stocks, API often times out)"""
        data = run_json(["call", "stock_zh_a_spot_em", "--limit", "5"], timeout=120)
        assert_dataframe_json(data, min_rows=5)

    def test_stock_zh_a_hist(self):
        """东方财富-A股历史行情-日线 (000001 平安银行)"""
        data = run_json([
            "call", "stock_zh_a_hist",
            "--symbol", "000001",
            "--period", "daily",
            "--start_date", "20240101",
            "--end_date", "20240110",
            "--adjust", "",
        ])
        assert_dataframe_json(data)
        assert "日期" in data["columns"] or "date" in str(data["columns"]).lower()

    def test_stock_zh_a_hist_qfq(self):
        """东方财富-A股历史行情-前复权"""
        data = run_json([
            "call", "stock_zh_a_hist",
            "--symbol", "600519",
            "--period", "daily",
            "--start_date", "20240101",
            "--end_date", "20240110",
            "--adjust", "qfq",
        ])
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_zh_a_st_em(self):
        """东方财富-风险警示板-ST股票 (slow: API often times out)"""
        data = run_json(["call", "stock_zh_a_st_em", "--limit", "5"], timeout=120)
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_zh_b_spot_em(self):
        """东方财富-B股实时行情 (slow: API often times out)"""
        data = run_json(["call", "stock_zh_b_spot_em", "--limit", "5"], timeout=120)
        assert_dataframe_json(data)

    def test_stock_sse_deal_daily(self):
        """上海证券交易所-每日概况"""
        data = run_json(["call", "stock_sse_deal_daily", "--date", "20250221"])
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_zh_a_new_em(self):
        """东方财富-新股实时行情 (slow: API often times out)"""
        data = run_json(["call", "stock_zh_a_new_em", "--limit", "5"], timeout=120)
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_intraday_em(self):
        """东方财富-盘口异动 (slow: API often disconnects)"""
        data = run_json(["call", "stock_intraday_em", "--symbol", "000001"])
        assert_dataframe_json(data)


# ─── Stock Shortcut Tests ────────────────────────────────────────────────────


@pytest.mark.network
class TestStockShortcuts:
    """Test stock shortcut commands (stock hist, stock spot)."""

    def test_stock_hist(self):
        """Shortcut: stock hist 000001 --json"""
        data = run_json([
            "stock", "hist", "000001",
            "--start", "20240101", "--end", "20240110",
        ])
        assert_dataframe_json(data)

    def test_stock_hist_weekly(self):
        """Shortcut: stock hist --period weekly"""
        data = run_json([
            "stock", "hist", "600519",
            "--period", "weekly",
            "--start", "20240101", "--end", "20240301",
        ])
        assert_dataframe_json(data)

    def test_stock_hist_qfq(self):
        """Shortcut: stock hist --adjust qfq"""
        data = run_json([
            "stock", "hist", "000001",
            "--adjust", "qfq",
            "--start", "20240101", "--end", "20240110",
        ])
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_stock_spot(self):
        """Shortcut: stock spot --json (slow: all A-shares, large dataset)"""
        data = run_json(["stock", "spot", "--limit", "5"], timeout=120)
        assert_dataframe_json(data, min_rows=5)


# ─── Futures Tests ───────────────────────────────────────────────────────────


@pytest.mark.network
class TestFuturesCommands:
    """Test futures-related CLI commands from documentation."""

    def test_futures_fees_info(self):
        """期货交易费用参照表"""
        data = run_json(["call", "futures_fees_info", "--limit", "5"])
        assert_dataframe_json(data)
        assert "合约代码" in data["columns"] or "交易所" in data["columns"]

    @pytest.mark.slow
    def test_futures_hist_em(self):
        """东方财富-期货历史行情 (slow: fetches full history then limits)"""
        data = run_json([
            "call", "futures_hist_em",
            "--symbol", "热卷主连",
            "--period", "daily",
            "--limit", "5",
        ], timeout=120)
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_futures_hist_table_em(self):
        """东方财富-期货合约表 (slow: large dataset)"""
        data = run_json(["call", "futures_hist_table_em", "--limit", "5"], timeout=120)
        assert_dataframe_json(data)

    def test_futures_contract_info_gfex(self):
        """广州期货交易所-合约信息"""
        data = run_json(["call", "futures_contract_info_gfex"])
        assert_dataframe_json(data)

    def test_futures_symbol_mark(self):
        """期货品种标记表"""
        data = run_json(["call", "futures_symbol_mark"])
        assert_dataframe_json(data)

    def test_futures_spot_price(self):
        """现货价格和基差"""
        data = run_json(["call", "futures_spot_price", "--date", "20240430"])
        assert_dataframe_json(data)


# ─── Futures Shortcut Tests ──────────────────────────────────────────────────


@pytest.mark.network
class TestFuturesShortcuts:
    """Test futures shortcut commands."""

    @pytest.mark.slow
    def test_futures_hist(self):
        """Shortcut: futures hist 热卷主连 --json (slow: fetches full history first)"""
        data = run_json([
            "futures", "hist", "热卷主连", "--limit", "5",
        ], timeout=300)
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_futures_list(self):
        """Shortcut: futures list --json (slow: API often times out)"""
        data = run_json(["futures", "list", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── Bond Tests ──────────────────────────────────────────────────────────────


@pytest.mark.network
class TestBondCommands:
    """Test bond-related CLI commands from documentation."""

    def test_bond_zh_cov(self):
        """可转债-概览表"""
        data = run_json(["call", "bond_zh_cov", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)

    def test_bond_cb_redeem_jsl(self):
        """集思录-可转债-强赎数据"""
        data = run_json(["call", "bond_cb_redeem_jsl"], timeout=60)
        assert_dataframe_json(data)

    def test_bond_china_yield(self):
        """中国债券-收益率曲线"""
        data = run_json([
            "call", "bond_china_yield",
            "--start_date", "20240101",
            "--end_date", "20240110",
        ])
        assert_dataframe_json(data)

    def test_bond_zh_us_rate(self):
        """中美国债收益率"""
        data = run_json(["call", "bond_zh_us_rate", "--start_date", "20240101"])
        assert_dataframe_json(data)

    def test_bond_spot_quote(self):
        """债券-做市报价"""
        data = run_json(["call", "bond_spot_quote", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_bond_cov_comparison(self):
        """可转债-对比表 (slow: API often disconnects)"""
        data = run_json(["call", "bond_cov_comparison", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)

    def test_bond_gb_zh_sina(self):
        """新浪-中国国债收益率 (10年期)"""
        data = run_json([
            "call", "bond_gb_zh_sina",
            "--symbol", "中国10年期国债",
        ])
        assert_dataframe_json(data)

    def test_bond_gb_us_sina(self):
        """新浪-美国国债收益率 (10年期)"""
        data = run_json([
            "call", "bond_gb_us_sina",
            "--symbol", "美国10年期国债",
        ])
        assert_dataframe_json(data)


# ─── Bond Shortcut Tests ────────────────────────────────────────────────────


@pytest.mark.network
class TestBondShortcuts:
    """Test bond shortcut commands."""

    def test_bond_convertible(self):
        """Shortcut: bond convertible --json"""
        data = run_json(["bond", "convertible", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── Macro Tests ─────────────────────────────────────────────────────────────


@pytest.mark.network
class TestMacroCommands:
    """Test macroeconomic CLI commands from documentation."""

    def test_macro_china_gdp_yearly(self):
        """中国GDP年率"""
        data = run_json(["call", "macro_china_gdp_yearly"])
        assert_dataframe_json(data)

    def test_macro_china_cpi_yearly(self):
        """中国CPI年率"""
        data = run_json(["call", "macro_china_cpi_yearly"])
        assert_dataframe_json(data)

    def test_macro_china_cpi_monthly(self):
        """中国CPI月率"""
        data = run_json(["call", "macro_china_cpi_monthly"])
        assert_dataframe_json(data)

    def test_macro_china_ppi_yearly(self):
        """中国PPI年率"""
        data = run_json(["call", "macro_china_ppi_yearly"])
        assert_dataframe_json(data)

    def test_macro_china_pmi_yearly(self):
        """中国官方制造业PMI"""
        data = run_json(["call", "macro_china_pmi_yearly"])
        assert_dataframe_json(data)

    def test_macro_china_lpr(self):
        """贷款市场报价利率 (LPR)"""
        data = run_json(["call", "macro_china_lpr"])
        assert_dataframe_json(data)

    def test_macro_china_m2_yearly(self):
        """M2货币供应量同比"""
        data = run_json(["call", "macro_china_m2_yearly"])
        assert_dataframe_json(data)

    def test_macro_china_exports_yoy(self):
        """中国出口额同比"""
        data = run_json(["call", "macro_china_exports_yoy"])
        assert_dataframe_json(data)

    def test_macro_china_trade_balance(self):
        """中国贸易差额"""
        data = run_json(["call", "macro_china_trade_balance"])
        assert_dataframe_json(data)

    def test_macro_china_lpi_index(self):
        """物流景气指数"""
        data = run_json(["call", "macro_china_lpi_index"])
        assert_dataframe_json(data)

    def test_macro_shipping_bdi(self):
        """波罗的海干散货指数"""
        data = run_json(["call", "macro_shipping_bdi"])
        assert_dataframe_json(data)

    def test_macro_china_shrzgm(self):
        """社会融资规模"""
        data = run_json(["call", "macro_china_shrzgm"])
        assert_dataframe_json(data)

    def test_macro_cnbs(self):
        """中国宏观杠杆率"""
        data = run_json(["call", "macro_cnbs"])
        assert_dataframe_json(data)

    def test_macro_china_urban_unemployment(self):
        """城镇调查失业率"""
        data = run_json(["call", "macro_china_urban_unemployment"])
        assert_dataframe_json(data)

    def test_macro_china_cx_pmi_yearly(self):
        """财新制造业PMI"""
        data = run_json(["call", "macro_china_cx_pmi_yearly"])
        assert_dataframe_json(data)


# ─── Macro Shortcut Tests ───────────────────────────────────────────────────


@pytest.mark.network
class TestMacroShortcuts:
    """Test macro shortcut commands."""

    def test_macro_gdp(self):
        """Shortcut: macro gdp --json"""
        data = run_json(["macro", "gdp"])
        assert_dataframe_json(data)

    def test_macro_cpi(self):
        """Shortcut: macro cpi --json"""
        data = run_json(["macro", "cpi"])
        assert_dataframe_json(data)

    def test_macro_cpi_yearly(self):
        """Shortcut: macro cpi --freq yearly --json"""
        data = run_json(["macro", "cpi", "--freq", "yearly"])
        assert_dataframe_json(data)


# ─── Forex Tests ─────────────────────────────────────────────────────────────


@pytest.mark.network
class TestForexCommands:
    """Test forex CLI commands."""

    @pytest.mark.slow
    def test_forex_spot_em(self):
        """外汇-实时行情 (slow: API often disconnects)"""
        data = run_json(["call", "forex_spot_em", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── Forex Shortcut Tests ───────────────────────────────────────────────────


@pytest.mark.network
class TestForexShortcuts:
    """Test forex shortcut commands."""

    @pytest.mark.slow
    def test_forex_spot(self):
        """Shortcut: forex spot --json (slow: API often disconnects)"""
        data = run_json(["forex", "spot", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── Index Tests ─────────────────────────────────────────────────────────────


@pytest.mark.network
class TestIndexCommands:
    """Test index CLI commands."""

    @pytest.mark.slow
    def test_index_spot_global(self):
        """Shortcut: index spot --json (slow: API often disconnects)"""
        data = run_json(["index", "spot", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── News Tests ──────────────────────────────────────────────────────────────


@pytest.mark.network
class TestNewsCommands:
    """Test news CLI commands."""

    def test_news_cctv(self):
        """央视新闻联播文字稿"""
        data = run_json(["call", "news_cctv", "--date", "20240101"], timeout=120)
        assert_dataframe_json(data)
        assert "title" in data["columns"]

    def test_news_economic_baidu(self):
        """百度经济数据日历"""
        data = run_json(["call", "news_economic_baidu"], timeout=60)
        assert_dataframe_json(data)

    def test_stock_news_main_cx(self):
        """财新网-财经新闻"""
        data = run_json(["call", "stock_news_main_cx", "--limit", "5"], timeout=60)
        assert_dataframe_json(data)


# ─── Output Format Tests ────────────────────────────────────────────────────


@pytest.mark.network
class TestOutputFormats:
    """Test --json / --csv / --output work in various positions."""

    def test_json_before_subcommand(self):
        """--json before subcommand: cli --json search stock"""
        result = run_cli(["--json", "search", "stock_zh_a_hist"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert data["count"] > 0

    def test_json_after_subcommand(self):
        """--json after subcommand: cli search stock --json"""
        result = run_cli(["search", "stock_zh_a_hist", "--json"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert data["count"] > 0

    def test_json_before_call_func(self):
        """--json between call and func_name: cli call --json macro_china_gdp_yearly"""
        data = run_json(["call", "--json", "macro_china_gdp_yearly"])
        assert_dataframe_json(data)

    def test_json_after_call_params(self):
        """--json at end of call: cli call news_cctv --date 20240101 --json"""
        data = run_json(["call", "news_cctv", "--date", "20240101"], timeout=120)
        assert_dataframe_json(data)

    def test_csv_output(self):
        """--csv output mode"""
        result = run_cli(["--csv", "call", "macro_china_gdp_yearly"])
        assert result.returncode == 0
        lines = result.stdout.strip().split("\n")
        assert len(lines) > 1, "CSV should have header + data rows"

    def test_limit_flag(self):
        """--limit restricts output rows"""
        data = run_json(["call", "macro_china_cpi_yearly", "--limit", "3"])
        assert data["total_rows"] == 3

    def test_output_to_csv_file(self):
        """--output saves to file"""
        with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
            filepath = f.name
        try:
            result = run_cli([
                "call", "macro_china_gdp_yearly",
                "--output", filepath,
            ])
            assert result.returncode == 0
            assert os.path.exists(filepath)
            assert os.path.getsize(filepath) > 0
            with open(filepath) as f:
                content = f.read()
            assert len(content.strip().split("\n")) > 1
        finally:
            if os.path.exists(filepath):
                os.unlink(filepath)

    def test_output_to_json_file(self):
        """--output saves to .json file"""
        with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
            filepath = f.name
        try:
            result = run_cli([
                "call", "macro_china_gdp_yearly",
                "--output", filepath,
            ])
            assert result.returncode == 0
            assert os.path.exists(filepath)
            with open(filepath) as f:
                data = json.load(f)
            assert isinstance(data, list) or isinstance(data, dict)
        finally:
            if os.path.exists(filepath):
                os.unlink(filepath)


# ─── Shortcut + JSON Combined Tests ─────────────────────────────────────────


@pytest.mark.network
class TestShortcutJsonCombined:
    """Test that shortcuts work with --json in various positions."""

    def test_stock_hist_json_after(self):
        """stock hist 000001 --json (--json after shortcut args)"""
        data = run_json([
            "stock", "hist", "000001",
            "--start", "20240101", "--end", "20240110",
        ])
        assert_dataframe_json(data)

    def test_macro_gdp_json_after(self):
        """macro gdp --json"""
        data = run_json(["macro", "gdp"])
        assert_dataframe_json(data)

    @pytest.mark.slow
    def test_futures_hist_json_after(self):
        """futures hist 热卷主连 --json --limit 3 (slow: full history fetch)"""
        data = run_json(["futures", "hist", "热卷主连", "--limit", "3"], timeout=300)
        assert_dataframe_json(data, min_rows=3)
